Skip to content
Merged
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
33 changes: 33 additions & 0 deletions .github/workflows/guest-languages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Experimental guest sources

on:
pull_request:
branches: [main]
push:
branches: [main]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: guest-languages-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
guest-languages:
runs-on: ubuntu-24.04
timeout-minutes: 25
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- run: bash scripts/ci-guest-languages.sh
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: always()
with:
name: guest-languages-${{ github.sha }}-${{ github.run_attempt }}
path: .cache/guest-languages/
include-hidden-files: true
if-no-files-found: error
retention-days: 14
2 changes: 2 additions & 0 deletions .gitleaksignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ internal/dispatcher/transport/api/auth_test.go:generic-api-key:28
# and lack of runtime authority are recorded in 209765cb7c74d04e70ef68ac289d7c1c4b764421.
11cfc7430a18964e308e5cd2426badc5630de19f:local-config/dispatcher/key:private-key:1
11cfc7430a18964e308e5cd2426badc5630de19f:local-config/executor/key:private-key:1
# Historical C fixture source-content checksum; explicit fields replace this layout.
b7d37f1fbd428ff2ce7f484c8f1793e90e4ea885:pkg/debuglet/testdata/guest_c/guest_c.wasm.json:generic-api-key:11
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ This directory is the versioned source for the [Debuglet documentation site](htt
- [Local validation checks](operations/local-checks.md)
- [Signed releases and offline verification](operations/releases.md)
- [External executor pilot](operations/external-pilot.md) — steps, records and support for an independently operated executor.
- [Abuse response](operations/abuse-response.md) — report handling, destination denials, credential recovery and owned local drills.

Use the [deployment guide](../deploy/README.md) for the maintained Ansible procedures and upgrade inputs.

Expand Down
111 changes: 86 additions & 25 deletions docs/development/guest-languages.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,85 @@ under those conditions. It is a measurement, not a support commitment.

## Measurements

Each guest prints one line. Every module was built in a pinned container and
run five times (three for Python with a filesystem) by a small runner that uses
wazero v1.12.0 with the executor's runtime and module configuration (the same
interpreter, memory limit and module options as
`internal/executor/debuglet/debuglet.go`, without the `env` host module, which
none of these hello guests imports). Peak resident memory is the runner
process's maximum RSS as reported by `/usr/bin/time -v`; the runner alone with
an empty module uses 6 MiB. Times are medians on one x86-64 Linux build host,
limited to three CPUs; treat them as orders of magnitude.

| Language | Toolchain | Build command | Module size | Build time | Engine compile | Start to first output | Peak RSS | Host operations available |
| --- | --- | --- | ---: | ---: | ---: | ---: | ---: | --- |
| Go | Go 1.26.8 | `GOOS=wasip1 GOARCH=wasm go build -trimpath` (`examples/debuglets/go/hello-local`) | 2,600,518 B | 3.8 s cold cache, 0.04 s warm | 116 ms | 14 ms | 69 MiB | All of ABI v1 and `debuglet_io_v1` through `pkg/debuglet`; arguments and output |
| Rust | rustc 1.90.0, `wasm32-wasip1` | `cargo build --release --target wasm32-wasip1` (`examples/debuglets/rust/helloworld`) | 65,127 B | 0.2 s | 3 ms | 0.6 ms | 9.3 MiB | TCP, TLS, listener and ICMPv4 through the experimental crate; arguments and output |
| C | wasi-sdk 25 (clang 19.1.5) | `clang -O2 main.c -lm` (`examples/debuglets/c/helloworld`) | 50,067 B | 0.07 s | 0.8 ms | 0.1 ms | 7.0 MiB | TCP, TLS, listener and ICMPv4 through the experimental header; arguments and output |
| JavaScript | Javy 9.1.0 (static module, QuickJS embedded) | `javy build -o hello.wasm hello.js` | 1,358,167 B | 4.9 s | 73 ms | 1.4 ms | 45 MiB | Output only: no `env` imports and no arguments |
| Python | CPython 3.14.7 WASI build (wasi-sdk 24), debug sections stripped | none (prebuilt interpreter, script passed with `-c`) | 7,630,023 B (30,522,756 B as released) | none | 255 ms | fails at startup | 158 MiB until the failure | None on the engine; see below |

Python with its standard library mounted read-only, which the engine does not
offer, printed its line after 1.5 s at a peak RSS of 212 MiB.
Each guest prints one line. The retained Rust/C figures below are from the
initial toolchain assessment. The Go, JavaScript and Python rows were reproduced
with the checked-in runner and inputs on x86-64 Linux. Times are medians of five
fresh processes (three for the Python filesystem comparison), not performance
thresholds. Peak RSS includes the runner process and compilation; it is not the
guest's linear memory. The runner uses the executor's wazero interpreter,
256 MiB memory limit, clocks and randomness, without networking host modules.
It records compilation separately from time to first stdout and stderr, so
Python's startup error is never counted as successful first output.

| Language | Toolchain | Module size | Build time | Engine compile | First stdout | Peak RSS | Host operations available |
| --- | --- | ---: | ---: | ---: | ---: | ---: | --- |
| Go | Go 1.26.8 | 2,600,518 B | 3.39 s cold, 0.036 s warm | 86 ms | 10 ms | 71 MiB | ABI v1 and `debuglet_io_v1` through `pkg/debuglet`; arguments and output |
| Rust | rustc 1.90.0, `wasm32-wasip1` | 65,127 B | 0.2 s | 3 ms | 0.6 ms | 9.3 MiB | Experimental TCP, TLS, listener and ICMPv4 bindings; arguments and output |
| C | wasi-sdk 25 (clang 19.1.5) | 50,067 B | 0.07 s | 0.8 ms | 0.1 ms | 7.0 MiB | Experimental TCP, TLS, listener and ICMPv4 bindings; arguments and output |
| JavaScript | Javy 9.1.0 static module | 1,358,212 B | 2.19 s | 60 ms | 1.2 ms | 47 MiB | Output only; no `env` imports or argument channel |
| Python | CPython 3.14.7 WASI build, stripped | 7,630,023 B | Prebuilt interpreter | 227 ms | Startup fails | 170 MiB | None on the engine |

The Python module is 30,522,756 bytes before stripping. With its standard
library mounted read-only, which the executor does not offer, it prints after
1.31 s at 203 MiB peak RSS. These are measurements, not a support commitment.

## Reproduce the measurements

On Linux amd64 with Docker, curl, gzip, `sha256sum` and Python 3:

```sh
bash scripts/measure-guest-languages.sh
```

The script keeps JSON samples, build times, toolchain versions and module
SHA-256 hashes under `.cache/guest-measure/`. It builds
`tools/guest-measure`, Go's `examples/debuglets/go/hello-local`, and the retained
`examples/debuglets/javascript/hello.js`; Python runs
`python -c "print('Hello from Debuglet! (Python)')"`. It checks the expected
missing-`encodings` startup failure without a filesystem, then runs the separate
filesystem diagnostic. The runner has a 30-second deadline and bounded output.
Go runs in the digest-pinned image in `deploy/ci/images.env` with three CPUs and
5 GiB RAM; the script records the actual Go and wazero versions. Javy runs as a
native Linux executable. Rust/C hello figures above used `cargo build --release
--target wasm32-wasip1` in `examples/debuglets/rust/helloworld` and wasi-sdk 25
`clang -O2 main.c -lm` in `examples/debuglets/c/helloworld`.

The download script checks these exact archives before extraction:

| Input | SHA-256 |
| --- | --- |
| [Javy 9.1.0 Linux amd64 gzip](https://github.com/bytecodealliance/javy/releases/download/v9.1.0/javy-x86_64-linux-v9.1.0.gz) | `a68b122d48eb3dfc1b801d4e14c39271fde3638243d3272d206e376ac9189e39` |
| [CPython 3.14.7 wasi-sdk 24 zip](https://github.com/brettcannon/cpython-wasi-build/releases/download/v3.14.7/python-3.14.7-wasi_sdk-24.zip) | `2e064d3fb8172471d39d741348efa722349c40b96301f69968dff714999c584b` |

The Python archive is Brett Cannon's experimental WASI build, not an official
CPython release artifact. Stripping uses `llvm-strip --strip-all` from wasi-sdk
25 pinned in the script. The measured modules have hashes:

- Go: `bcdf89c2102c404ab40091acef44ff00fbd5dd6e77f3bdd64b09caff3b5b3f8a`.
- JavaScript: `853baf0024ebbe9aa40784b69e45cd296e68b2c620d0a26386d9e0f90e901ba8`.
- Python: `7fe2dead89e0f64016c79142c0badd45e95b66808fc30ea904d149deda92b8a0`.

## Rust and C source and consumer checks

```sh
bash scripts/ci-guest-languages.sh
```

The `guest-languages` CI job runs this same script. It rebuilds the retained
TCP fixtures with digest-pinned Rust 1.90 and wasi-sdk 25 images, requires exact
binary equality, and checks source hashes in their `.wasm.json` records. It
also packages the Rust crate with its license, copies the C header and license,
and builds consumers in empty directories from those deliverables. Both fresh
consumers run against the current executor host for a TCP read larger than the
ABI buffer followed by EOF, listener echo, and connection-refusal behavior.
Artifacts include the source packages, consumer modules, SHA-256 manifest and
JSON test results under `.cache/guest-languages/`.

This validates local source packaging and the tested TCP subset. It does not
publish a crate or release, cover every binding, or establish a maintainer and
support policy. Changing a binding requires deliberately rebuilding and
updating the retained module's source and binary record together; a stale
fixture fails the checks.

## JavaScript

Expand Down Expand Up @@ -70,15 +129,17 @@ The maintainers decide; this is the proposal the measurements support.
and the repository builds and tests it.
- **Rust and C: experimental.** They are small and fast, and the test suite
runs one retained guest per language on the engine, but their bindings cover
part of the ABI and no CI lane builds them. Promoting either would need the
missing bindings (UDP, address getters, `drain_connection`,
`debuglet_io_v1`), a CI build with the pinned toolchain and an owner.
part of the ABI. CI rebuilds their sources and tests fresh package consumers.
Promoting either still needs the missing bindings (UDP, address getters,
`drain_connection`, `debuglet_io_v1`), a release/distribution decision and an
accountable owner accepting the support policy.
- **JavaScript: unsupported.** A guest can print but cannot take arguments or
measure anything. Supporting it would mean maintaining a Javy plugin that
exposes the ABI, an argument channel, a pinned Javy release and a CI lane.
- **Python: unsupported.** It does not start on the engine. Supporting it would
mean maintaining a custom CPython WASI build with an embedded standard
library and an ABI extension module, and accepting its start-up cost.

No runtime, build path or CI lane for JavaScript or Python is part of the
repository.
The JavaScript/Python scripts are reproducible assessment tools, not supported
SDKs or executor runtime additions. No maintainer support decision is implied
by these measurements.
91 changes: 91 additions & 0 deletions docs/operations/abuse-response.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Abuse reports and destination opt-outs

Before operating a deployment, name an accountable reporting contact, a private
intake channel and the people allowed to read incident evidence. Agree who can
approve destination denials, revoke credentials and escalate an unresolved
report. The public issue tracker is suitable for requesting that contact, not
for packet captures, addresses, credentials or personal information. This
procedure does not designate a reporting contact or establish that the team has
agreed an evidence-access policy.

## Intake and approval

Open a private incident record with a time, incident identifier, report source,
claimed destination, requested action, reviewer and access list. Verify control
of the destination through the deployment's agreed private channel. An account
login by itself proves neither destination ownership nor authority to approve
an opt-out. Record the approval and the basis for that decision before changing
policy. Use the existing [security reporting policy](../../SECURITY.md) for a
suspected vulnerability.

## Apply and verify a destination denial

An authenticated operator submits `PATCH /destination` with the destination,
`"denied": true` and a concise reason, then reads `GET /destinations`. Preserve
the returned actor, revision, reason, timestamps, recipient count and delivery
state in the incident record. Keep sensitive evidence outside the reason field.
The denial persists across dispatcher restarts and refuses new work. A bandwidth
limit of zero is not a substitute for a denial.

HTTP 204 confirms delivery to the relevant executor recipients. A recorded but
unconfirmed change remains an incident action requiring observation; it does not
prove traffic stopped. A peer that cannot receive the denial is retired after
the bounded delivery attempt, and a lost control lease stops its active work.
Allow the delivery bound and the configured lease to expire before classifying
that recovery path. Read the recorded delivery result again after reconnect.

Verify the destination's actual receiver observations separately from the
acknowledgment. For owned fixtures, establish nonzero TCP and UDP traffic before
the denial, record the approval and delivery times, then record TCP termination
and a bounded quiet interval after queued UDP datagrams drain. Record those
durations and packet/byte counts, not an unqualified claim that all traffic
stopped forever. An attachment-presence observation, a closed socket or a quiet
interval alone does not validate every protocol, network path or packet counter.

Escalate persistent traffic or unconfirmed delivery to the named operator. Stop
the affected executor or revoke its enrollment under the deployment's approved
incident procedure if the denial cannot be established. Preserve the original
record and record further actions and observations; do not overwrite a failed
attempt with a later success.

## Copied credentials

Follow [Respond to a copied credential](authentication.md#respond-to-a-copied-credential).
From an uncompromised browser session, identify and revoke the affected
credential, or all account credentials if its identity is uncertain. Verify
that a formerly successful authenticated request with the old credential is now
refused. Record only its identifier, never the secret. When the legacy account
key is exposed, use the separately retained recovery code, confirm the old key
and old sessions are refused, and sign in with the replacement key. Account
recovery does not establish recovery of an external identity provider.

Credential revocation prevents subsequent authenticated requests. It does not
cancel admitted measurements: handle active work and observe the receiver
separately. Preserve the account identifier, credential identifier, times,
actions, observed results, reviewer and record-access policy privately.

## Owned local rehearsal

On supported Linux, the existing control-path fixture and credential drill run
without an outside host, identity provider or chain:

```sh
go test -race ./internal/executor \
-run 'TestDestinationDenyControlPathAndReconnect|TestCredentialCompromiseDrill' \
-count=1 -v
```

The destination fixture uses real local SQLite, HTTP authentication and role
checks, dispatcher/executor control channels, and loopback TCP/UDP receivers. It
exercises acknowledged delivery and lost policy delivery while ordinary control
probes still succeed. Its runtime adapter sends through admitted and registered
sockets; it is not an independently operated deployment or an arbitrary guest
validation. The credential drill exercises the real API's issue, use, revoke,
refuse and recover transitions with a local account. Neither drill uses the
local authentication bypass. The log records identifiers and observations, not
tokens, account keys, recovery codes or packet contents.

Keep the exact source revision, command, complete result, observed traffic
counts and timing in the private incident record. This technical rehearsal does
not replace a rehearsal with the actual reporting contact and agreed handling
and evidence-access policy.
Loading