Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 68 additions & 45 deletions compiler/aetherc.c

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 9 additions & 8 deletions docs/aether-embedded-in-host-applications.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ applications (Java, Go, etc.).
> for Python, Java (Panama, JDK 22+), Ruby (Fiddle). Java SWIG is no
> longer the recommended path, Panama is cleaner once you have a
> namespace manifest to template against
> - Part 3, capability-empty default (rejects `std.net|http|tcp|fs|os`)
> - Part 3, capability-empty default (rejects network, filesystem, OS, and
> foreign-runtime module families)
> - Part 4, LD_PRELOAD containment composes naturally with `--emit=lib`
>
> What's still speculative (📋 Future):
Expand Down Expand Up @@ -479,18 +480,18 @@ Any config language that executes as real code introduces risks that
static config (JSON, TOML) doesn't have:

**1. Network access.** If the embedded Aether runtime has access to
socket APIs, a config file could open a listener, exfiltrate data, or
act as a backdoor. The embedding mode should **not link `std.tcp`,
`std.http`, or `std.net`** by default. The facade should be the *only*
socket APIs, a config file could open a listener or send data outside the
host. The embedding mode should **not link `std.tcp`, `std.udp`,
`std.http` (including its children), or `std.net`** by default. The facade should be the *only*
way the script talks to the outside world.

**2. File system access.** A config script that can read `/etc/passwd`
or write to `/tmp` is a liability. The embedding mode should **not link
`std.fs` or `std.file`** unless the host explicitly opts in.
`std.fs`, `std.file`, or `std.dir`** unless the host explicitly opts in.

**3. Process spawning.** `os_system()` and `os_run()` should be
unavailable in embedded mode. A config file that can `exec` arbitrary
commands is not a config file, it's a remote code execution vector.
**3. OS integration.** `os_system()`, `os_run()`, dynamic loading through
`std.dl`, and foreign runtimes through `contrib.host.*` should be unavailable
in embedded mode unless the host explicitly grants the corresponding category.

**4. Unbounded computation.** A config script with an infinite loop
hangs the host. The embedding runtime should support a **wall-clock
Expand Down
5 changes: 3 additions & 2 deletions docs/design/aether-dsl-as-a-rules-engine.md
Original file line number Diff line number Diff line change
Expand Up @@ -384,8 +384,9 @@ A `rules` module providing:

### In the compiler

- ✅ **Done**: shared library output (`aetherc --emit=lib`), with
capability-empty link profile rejecting `std.net|http|tcp|fs|os`.
- ✅ **Done**: shared library output (`aetherc --emit=lib`), with a
capability-empty link profile covering network, filesystem, OS, and
foreign-runtime module families.
Renamed from the original `--embed` proposal to fit alongside
`--emit=exe` and `--emit=both`.
- 📋 **Future**: `ae eval` subcommand for the subprocess model
Expand Down
25 changes: 13 additions & 12 deletions docs/emit-lib.md
Original file line number Diff line number Diff line change
Expand Up @@ -440,9 +440,10 @@ way you would treat any other cross-language value exchange.

`--emit=lib` rejects `.ae` files that import:

- `std.net`, `std.http`, `std.tcp` networking
- `std.fs` filesystem
- `std.os` process / environment / shell
- `std.net`, `std.http` and its children, `std.tcp`, `std.udp` networking
- `std.fs`, `std.file`, `std.dir` filesystem
- `std.os`, `std.dl` process, environment, shell, and dynamic loading
- `contrib.host.*` foreign-language runtimes

The rationale: a library embedded in a host process shouldn't have
ambient network, filesystem, or process-spawning access. Those are
Expand All @@ -456,9 +457,9 @@ other capability-free standard modules are allowed.
It also rejects an `extern` declared in the program's own code (the entry
file, an `@extern("…")` binding, or a local module it imports). An extern
names a C function directly, so `extern system(cmd: string) -> int` would
reach what `import std.os` is gated to stop. Externs declared by `std.*` and
`contrib.*` modules are not the program's and are unaffected: those modules
sit behind the gates above. Opt in with `--with=extern`.
reach what `import std.os` is gated to stop. Externs declared by other `std.*`
modules are not the program's and are unaffected. Foreign-language host modules
sit behind the `contrib.host.*` gate above. Opt in with `--with=extern`.

### Opting in: `--with=<capabilities>`

Expand All @@ -467,13 +468,13 @@ handwritten C into one binary, rather than embedding Aether as an
untrusted user script, opt into specific capability categories:

```sh
ae build --emit=lib --with=fs file.ae # std.fs
ae build --emit=lib --with=net file.ae # std.net, std.http, std.tcp
ae build --emit=lib --with=os file.ae # std.os
ae build --emit=lib --with=fs file.ae # std.fs, std.file, std.dir
ae build --emit=lib --with=net file.ae # std.net/http/tcp/udp families
ae build --emit=lib --with=os file.ae # std.os, std.dl
ae build --emit=lib --with=fs,os file.ae # multiple, comma-separated
ae build --emit=lib --with=extern file.ae # the program's own C externs
ae build --emit=lib --with=first-party file.ae # alias for fs,net,os
ae build --emit=lib --with=all file.ae # alias for fs,net,os
ae build --emit=lib --with=extern file.ae # own C externs, contrib.host.*
ae build --emit=lib --with=first-party file.ae # alias for fs,net,os,extern
ae build --emit=lib --with=all file.ae # alias for fs,net,os,extern
```

The gate stays default-deny: a build without `--with=fs` still rejects
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- **Shared-library capability checks now cover aliases and module families.**
`--emit=lib` now applies the filesystem gate to `std.file` and `std.dir`,
the network gate to `std.udp` and nested `std.http` modules, the OS gate to
`std.dl`, and the extern gate to `contrib.host.*`.
12 changes: 8 additions & 4 deletions tests/integration/emit_lib_banned/test_emit_lib_banned.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/bin/sh
# Verifies --emit=lib rejects every capability-heavy stdlib import.
# All five should produce the capability-empty error and a non-zero
# exit status.
# Verifies --emit=lib rejects every capability-heavy module family.
# Each case should produce the capability-empty error and a non-zero
# exit status, including aliases and nested modules.

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
Expand All @@ -25,7 +25,11 @@ check_banned() {
fi
}

for mod in std.net std.http std.tcp std.fs std.os; do
for mod in \
std.net std.http std.http.client std.tcp std.udp \
std.fs std.file std.dir \
std.os std.dl \
contrib.host.python; do
check_banned "$mod"
done

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
# — that's pinned by the existing emit_lib_banned test. This test pins
# the opt-in direction:
#
# 1. --with=fs unlocks std.fs but still rejects std.net / std.os.
# 2. --with=net unlocks std.net / std.http / std.tcp but not std.fs.
# 3. --with=os unlocks std.os.
# 1. --with=fs unlocks the filesystem module family but still rejects net / os.
# 2. --with=net unlocks the network module family but not fs.
# 3. --with=os unlocks the operating-system module family.
# 4. --with=fs,os unlocks both in one invocation.
# 5. --with=<unknown> is a hard error, not a silent no-op.
# 6. --with=fs WITHOUT --emit=lib is a no-op (no error, no effect).
Expand Down Expand Up @@ -53,19 +53,29 @@ run_case() {
# ---- 1. --with=fs unlocks std.fs, still rejects std.net / std.os ----

run_case "--with=fs accepts std.fs" std.fs "--emit=lib --with=fs" accept
run_case "--with=fs accepts std.file" std.file "--emit=lib --with=fs" accept
run_case "--with=fs accepts std.dir" std.dir "--emit=lib --with=fs" accept
run_case "--with=fs still rejects std.net" std.net "--emit=lib --with=fs" reject
run_case "--with=fs still rejects std.os" std.os "--emit=lib --with=fs" reject

# ---- 2. --with=net unlocks net/http/tcp, not fs ----

run_case "--with=net accepts std.net" std.net "--emit=lib --with=net" accept
run_case "--with=net accepts std.http" std.http "--emit=lib --with=net" accept
run_case "--with=net accepts std.http.client" std.http.client "--emit=lib --with=net" accept
run_case "--with=net accepts std.tcp" std.tcp "--emit=lib --with=net" accept
run_case "--with=net accepts std.udp" std.udp "--emit=lib --with=net" accept
run_case "--with=net still rejects std.fs" std.fs "--emit=lib --with=net" reject

# ---- 3. --with=os unlocks std.os ----

run_case "--with=os accepts std.os" std.os "--emit=lib --with=os" accept
run_case "--with=os accepts std.dl" std.dl "--emit=lib --with=os" accept

# ---- 3b. contrib.host.* requires the explicit extern capability ----

run_case "--with=extern accepts contrib.host.python" contrib.host.python "--emit=lib --with=extern" accept
run_case "--with=os still rejects contrib.host.python" contrib.host.python "--emit=lib --with=os" reject

# ---- 4. --with=fs,os unlocks both in one invocation ----

Expand Down