Skip to content

inoxlang/inox

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Inox

a shield

Inox is a single-binary platform that will ultimately contain all you need to develop, test, and deploy web apps. Applications are developped using Inoxlang, a high-level programming language that deeply integrates with several components:

  • A built-in database that stores data structures (e.g. objects, lists, sets, maps, message threads)
  • An HTTP server with filesystem routing
  • A testing engine supporting virtual filesystems and temporary databases (completely transparent for application code).
  • An in-process container engine: each application runs in a dedicated virtual filesystem, and is subject to permissions. This is not related to Linux containers.

Here are a few example files that are part of a basic todo app with accounts and persistence.

image

πŸ“œ Database schema

image

βš™οΈ A request handler (filesystem routing).

Each handler module runs in a dedicated execution context with its own permissions.

image

Note: the permissions granted to imported modules (local or third-party) are explicit: import lib ./malicious-lib.ix { allow: {} }

βš™οΈ HTTP GET handler with HTML rendering

image

βœ… Testing engine example

image

Inox is intended to be used for web applications that are primarily rendered server-side. Client-side interactivity is implemeted using Hyperscript and HTMX. See here for more information: frontend development.


I have been working over 2 years on Inox. There is still work to do in order to make Inox usable in real world applications. If you believe this project has potential, consider donating through GitHub (preferred) or Patreon. It will help me continue working on Inox (check see 'What is planned' and Other features).

⬇️ Installation
πŸ” Application Examples
πŸ“š Learning Inox
⭐ Other features
πŸ‘₯ Discord Server & Subreddit
❔ Questions you may have

πŸ—“οΈ What is planned ?

  • Finish the transaction system and support persisting most data-structure types with accepable performance.
  • Automated database backups in S3-compatible storage.
  • A user interface for viewing and modifying the schema and data of databases.
  • Log persistence in S3 (note that Inox has builtins for structured logging.
  • Support automated deployments on popular cloud providers
  • Storage of secrets in key management services (e.g. GCP KMS, AWS KMS). Secrets are special Inox values that cannot be printed, logged or serialized.
  • Develop a standard library
  • Integrate a subset of Git (using https://github.com/go-git/go-git and https://code.visualstudio.com/api/extension-guides/scm-provider)
  • Support no-downtime upgrades
  • WebAssembly support using https://github.com/tetratelabs/wazero
  • Team access control for Inox projects
  • Improve execution performance and memory usage
  • Finalize the implementation of structs and implement a Low Level VM.
  • Allow developers to define custom builtins written in Go (note: building inox is just go build ./cmd/inox)
  • Analytics
  • And more !

🎯 Goals

  • Zero boilerplate
  • Dead simple configuration
  • Super stable (once version 1.0 is reached)
  • Secure by default
  • Low maintenance
  • A programming language as simple as possible
  • Multi-node support that is almost transparent for application code

❌ Non Goals

  • Be a suitable solution for 100% of real-world web projects.
  • Support any database for storing domain data (users, ...).
  • Be super fast
  • Be able to store several tera bytes of non-blob data. Note that blob data and backups will be stored in S3 buckets.
  • Support more than ~ 10k concurrent users
  • Interactivity with native code (FFIs ...)

Development Environment - Inox Project Server

There is no true local development environment when developing Inox projects. The code editor connects to a project server (included in the Inox binary) and the code is edited and tested inside a virtual workspace. The server does not yet integrate git yet, but this is planned, https://github.com/go-git/go-git.

Each developer in the project will ultimately have its own copy. As of now the project server only supports projects with a single developer.

Pros

  • All developers in the project will have an identical development environment.
  • Code conflicts could be detected early, even before a developer commmits.
  • Dependency downloads should be faster because they are made by the project server, which is expected to run on machine in a datacenter. Also downloaded dependencies can be shared between developers.
  • The project does not need to be downloaded by the developer.

Cons

  • An Internet connection is required. However code files are cached on the developer's machine for offline access (read only for now).

Project servers are expected to run on a dedicated machine. They will support automatic infrastructure management in the near future.

βš™οΈ Details

The project server is a LSP server with additional custom methods. It enables the developer to develop, debug, test, deploy and manage secrets, all from VSCode.

flowchart TB

subgraph VSCode
  VSCodeVFS(Virtual Filesystem)
  Editor
  Editor --> |persists edited files in| VSCodeVFS
  DebugAdapter
end

Editor(Editor) --> |standard LSP methods| ProjectServer

VSCodeVFS --> |"custom methods (LSP)"| DeveloperCopy

DebugAdapter(Debug Adapter) -->|"Debug Adapter Protocol (LSP wrapped)"| Runtime(Inox Runtime)

subgraph ProjectServer[Project Server]
  Runtime
  DeveloperCopy(Developer's Copy)
end

ProjectServer -->|manages| Infrastructure(Infrastructure)
ProjectServer -->|gets/sets| Secrets(Secrets)

Installation

graph LR

subgraph VSCode["VSCode (any OS)"]
  direction LR
  InoxExtension(Inox Extension)
end

InoxExtension -->|"LSP (WebSocket)"| ProjectServer

subgraph InoxBinary["Inox binary (Linux)"]
  ProjectServer(Project Server)
end

Inox applications can currently only be developed using the Inox extension for VSCode and VSCodium. You can install the inox binary on your local (Linux) machine, a local VM, or a remote machine.

Local Installation

  • Download the latest release

    wget -N https://github.com/inoxlang/inox/releases/latest/download/inox-linux-amd64.tar.gz && tar -xvf inox-linux-amd64.tar.gz
    
  • Install inox to /usr/local/bin

    sudo install ./inox -o root -m 0755 /usr/local/bin/inox
    
  • Delete the files that are no longer needed

    rm ./inox inox-linux-amd64.tar.gz
    
  • Install the VSCode/VSCodium extension. Make sure to read the Requirements and Usage sections in the extension's details.

Installation on a remote machine (VPS)

This install is not recommended for now since there are potentially memory leaks.

  • Install the Inox Daemon

  • Install the VSCode/VSCodium extension. Make sure to read the Requirements and Usage sections in the extension's details.

If you have any questions you are welcome to join the Discord Server or the Subreddit. If you want to build Inox from source go here.

Application Examples

More examples will be added soon.

Learning Inox

You can learn the language directly in VSCode by creating a file with a .tut.ix extension. Make sure to create this file inside an Inox project.

tutorial-demo

πŸ“š Tutorials
🌐 Frontend development
🧰 Builtins
πŸ—‚οΈ Collections
πŸ“– Language reference
πŸ“– HTTP Server reference

If you have any questions you are welcome to join the Discord Server.

Scripting

Inox can be used for scripting & provides a shell. The development of the language in those domains is not very active because Inox primarily focuses on Web Application Development.

To learn scripting go here. View Shell Basics to learn how to use Inox interactively.

Build From Source

  • Clone this repository
  • cd into the directory
  • Run go build ./cmd/inox

Other Features

Structured Logging

structured logging demo

Documentation

Secrets

Secrets are special Inox values, they can only be created by:

  • Defining an environment variable with a pattern like %secret-string
  • Storing a project secret
  • Built-in functions (e.g. a function returning a private key)

Secrets have special properties:

  • The content of the secret is hidden when printed or logged.
  • Secrets are not serializable, so they cannot be included in HTTP responses.
  • A comparison involving a secret always returns false.
manifest {
    ...
    env: %{
        API_KEY: %secret-string
    }
    ...
}

API_KEY = env.initial.API_KEY

Demo of project secrets

project secrets demo

Built-in Browser Automation

h = chrome.Handle!()

h.nav https://go.dev/
node = h.html_node!(".Hero-blurb")
h.close()

Documentation

Examples

Browser automation is quite buggy right now, I need to improve the configuration of https://github.com/chromedp/chromedp.

Composable String Patterns

Inox has composable string patterns that are more readable than regular expressions (Inox still supports regexps though). Let's see an example, here is the regexp for matching time (e.g. 01:45 AM).

/^(?<hour>(1[0-2]|0?[1-9])):(?<minute>[0-5][0-9]) (?<period>AM|PM)$/

//Same regexp but without group names.
/^(1[0-2]|0?[1-9]):([0-5][0-9]) (AM|PM)$/

Inox string pattern:

image

Composed Inox string pattern:

image

Pattern equivalent to the regex ^a+b{8}c*$.

image

Recursive string patterns (WIP)

pattern json-list = @ str( 
    '[' 
        (| atomic-json-val
         | json-val 
         | ((json-val ',')* json-val) 
        )? 
    ']'
)

pattern json-val = @ str(| json-list | atomic-json-val)
pattern atomic-json-val = "1"

⚠️ Recursive string patterns are not intended to be used for validating or parsing large inputs.

Context Data

The context of module instances can contain data entries that can be set only once. Child modules have access to the context data of their parent and can individually override entries.

add_ctx_data(/lang, "en-US")

...

fn print_error(){
  lang = ctx_data(/lang)
  ...
}

The HTTP server adds a /session entry to the handling context of a request if the session-id cookie is present.

Recursive Execution Cancellation

The context of each module instance can be cancelled. If you are familliar with Golang: this is analogous to the cancellation of a context.Context.

The cancellation of a context causes all operations to stop:

  • Execution of the module.
  • Pending I/O operations such as HTTP requests and filesystem operations are cancelled.
  • Child modules are recursively cancelled as well. Lightweight threads are stopped.

Lightweight threads

lthread = go {} do {
  print("hello from lightweight thread !")
  return 1
}

# 1
result = lthread.wait_result!()

Lthread Groups

group = LThreadGroup()
lthread1 = go {group: group} do read!(https://jsonplaceholder.typicode.com/posts/1)
lthread2 = go {group: group} do read!(https://jsonplaceholder.typicode.com/posts/2)

results = group.wait_results!()

More Secure Path Interpolations

In Inox interpolations are always restricted in order to prevent injections and regular strings are never trusted. URLs & paths are first-class values and must be used to perform network or filesystem operations.

filepath = ./go
/home/user/{filepath} # /home/user/go

filepath = ../../etc/shadow # malicious user input
/home/user/{filepath} 
# error: result of a path interpolation should not contain any of the following substrings: '..', '\', '*', '?'

This is still work in progress, Allowing specific dangerous substrings may be supported in the future.

More Secure URL Interpolations

URL interpolations are restricted based on their location (path, query).

https://example.com/{path}?a={param}

In short, most malicious path and param values provided by a malevolent user will cause an error at run time.

Click for more explanations.

Let's say that you are writing a piece of code that fetches public data from a private/internal service and returns the result to a user. You are using the query parameter ?admin=false in the URL because only public data should be returned.

public_data = http.read!(https://private-service{path}?admin=false)

The way in which the user interacts with your code is not important here, let's assume that the user can send any value for path. Obviously this is a very bad idea from a security standpoint. A malicious path could be used to:

  • perform a directory traversal if the private service has a vulnerable endpoint
  • inject a query parameter ?admin=true to retrieve private data
  • inject a port number

In Inox the URL interpolations are special, based on the location of the interpolation specific checks are performed:

https://example.com/api/{path}/?x={x}
  • interpolations before the '?' are path interpolations
    • the strings/characters '..', '\\', '?' and '#' are forbidden
    • the URL encoded versions of '..' and '\\' are forbidden
    • ':' is forbidden at the start of the finalized path (after all interpolations have been evaluated)
  • interpolations after the '?' are query interpolations
    • the characters '&' and '#' are forbidden

In the example if the path /data?admin=true is received the Inox runtime will throw an error:

URL expression: result of a path interpolation should not contain any of the following substrings: "..", "\" , "*", "?"

Isolation of Dependencies

Permissions granted to the imported modules are specified in the import statements.

./app.ix

manifest {
  permissions: {
    read: %/...
    create: {threads: {}}
  }
}

import lib ./malicious-lib.ix {
  arguments: {}
  allow: {
    read: %/tmp/...
  }
}

./malicious-lib.ix

manifest {
  permissions: {
    read: %/...
  }
}

data = fs.read!(/etc/passwd)

If the imported module asks more permissions than granted an error is thrown:
import: some permissions in the imported module's manifest are not granted: [read path(s) /...]

Process-Level Access Control

In addition to the checks performed by the permission system, the inox binary uses Landlock to restrict file access for the whole process and its children.

Dropping Permissions

Sometimes programs have an initialization phase, for example a program reads a file or performs an HTTP request to fetch its configuration. After this phase it no longer needs some permissions so it can drop them.

drop-perms {
  read: %https://**
}

Per-Module Limits

(WIP)

Limits limit intensive operations, there are three kinds of limits: byte rate, frequency & total. They are defined in the manifest and are shared with the children of the module.

manifest {
    permissions: {
        ...
    }
    limits: {
        "fs/read": 10MB/s
        "http/req": 10x/s
    }
}

By default strict limits are applied on HTTP request handlers in order to mitigate some types of DoS.

Learn more

Questions You May Have

Why isn't Inox using a container runtime such as Docker ?

Because the long term goal of Inox is to be a simple, single-binary and super stable platform for applications written in Inoxlang and using libraries compiled to WASM.
Each application or service will ultimately run in a separate process:

  • filesystem isolation is achieved by using virtual filesystems (meta filesystem)
  • process-level access control is achieved using Landlock. (This is still work in progress).
  • fine-grained module-level access control is already achieved by Inox's permission system
  • process-level resource allocation and limitation will be implemented using cgroups
  • module-level resource allocation and limitation is performed by Inox's limit system

Can native binaries be executed inside the virtual filesystem ?

No but executing WebAssembly modules and programs will be possible.

Does Inox use a single process ?

  • The inox daemon runs in a single dedicated process.
  • As of now the project server and applications launched by developers run in the same process. This is obviously a temporay solution.

In the planned architecture every important component runs in a separate inox process.

graph TB

Inoxd(inoxd) --> |$ inox project-server -config='...'| ProjectServer("Project Server (may be on a dedicated machine)")
Inoxd --> |spawns| NodeAgent
NodeAgent("Node Agent \n [uses cgroups]") --> |creates process| DeployedApp1(Deployed Application 1)
NodeAgent --> |creates process| DeployedApp2(Deployed Application 2)
NodeAgent --> |creates process| ServiceModule(Separate Service of App 1)
ServiceModule -..- DeployedApp1

ProjectServer --> |asks to deploy/stop apps| NodeAgent

Why have you created Inox ?

Before reading the answser please make sure to read the Goals & Non Goals sections.

I like creating programming languages. At the beginning Inox was not even about full stack development. It quickly evolved towards this use case because I am tired of accidental complexity in full stack development. I particularly hate having to glue and import components that are just needed 99% of the time. I don't like spending hours configuring stuff, a bit of configuration is fine though. Local development environments are also a pain to setup sometimes. (There is no true local dev environment when developing Inox projects).

Inox being an opinionated high-level programming language / high level platform it obviously has pros and cons. Also when using a new programming language you don't have access to a rich ecosystem. In other words Inox currently does not bring all the potential value it could bring.

Does Inoxlang support multi-threading ?

Inoxlang uses goroutines under the hood and allows the developer to create lighweight threads. The HTTP server is built upon Golang's HTTP server: each request is handled in a dedicated goroutine that runs a bytecode interpreter.

Is Inoxlang fast ?

Each Inoxlang module is executed by a bytecode interpreter. As of now this interpreter is not optimized, yet. Also since it is intended to execute businness logic it always checks for integer overflow/underflow in arithmetic operation. Doing this has a cost.

I plan to optimize the interpreter and add an additional, lower-level, interpreter that will execute low-level mode code only. This includes struct methods and low-level functions. In this mode only a few types are available (structs, int, float, bool, string, ...). High-level data structures such as objects and lists are primarly designed to represent domain data and to interact with the database. They are not designed to be ultra fast.

I also need to improve the creation of interpreters for request handlers so that it's fast and cheap in memory.

Is Inoxlang sound ?

No, Inoxlang is unsound. However:

  • The type system is not complex, and I do not plan to add many features impacting it. I will not add object methods nor classes.
  • Type assertions using the assert keyword are checked at run time.
  • The any type is not directly available to the developer, and it does not disable checks like in Typescript. It is more similar to unknow.

*Types like Set are kind of generic but it cannot be said that generics are implemented.

The type checking system not being implemented in a classic way, that will lead to some limitations and a bit less safe checks. However the type checking logic is not expected to grow much. Therefore the vast majority issues should be resolved by testing extensively.

What architectures and operating systems are supported ?

As of now, only Linux distributions on the AMD64 architecture are supported.

  • Support for Linux and MacOS on ARM64 will likely be implemented.
  • Windows support will not be implemented. If you use this OS you should be able to run Inox on WSL.

What is the state of the codebase (quality, documentation, tests) ?

As of now, certain parts of the codebase are not optimally written, lack sufficient comments and documentation, and do not have robust test coverage.

Early Sponsors

Lexter
Lexter
Datamix.io
Datamix.io

Consider donating through GitHub (preferred) or Patreon. Thank you !

⬇️ Installation
πŸ” Application Examples
πŸ“š Learning Inox
πŸ‘₯ Discord Server & Subreddit

Back To Top