You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit b3dfd41
Browse filesBrowse the repository at this point in the historyBrowse files
feat(s110): Lua in the BEAM, under limits the machine enforces
A run_lua tool and skill-owned scripts run Lua inside this process: no operating
system process, no filesystem handle, no socket. A model asked to total a column
produces a number that looks right; a script produces one that is right and leaves
the script in the transcript.
The limits are the virtual machine's rather than a promise. The runner sets its own
max_heap_size and is killed at the boundary without this code being involved, a
wall clock bounds the run, and the pool refuses an over-limit run by name rather
than queueing it invisibly, because a run that waits an unbounded time to start has
no wall-clock bound at all.
One limit is reported rather than claimed. luerl_sandbox enforces max_reductions by
polling with a 100 ms sleep between checks, so caps of 1,000 and of 1,000,000 both
stop a tight loop at 20 to 34 million reductions. Reductions are a statistic here
and the bound that stops a runaway script is time; docs/sandbox.md carries the
measurements rather than the claim.
What a script wants done it asks for through Effects.Runner.execute/3, the executor
a model's own call uses, so it is decided by the same gate and leaves the same
receipts. A test asserts a denied call gives the script nothing, because that is
what would make Lua a second door. A call needing a person fails immediately rather
than holding the sandbox open: a run is bounded in seconds and a person is not, and
letting a script block would turn every approval into a timeout.
A skill may ship a script under its own scripts/ directory. Where it may read is
checked by resolving the path rather than trusting it, so a traversal is caught by
where it lands. script_path/2 is public so the test exercises the real function; the
first version re-implemented the check and asserted against its own copy, which is
the one way to make a security test meaningless.
The skill scanner gained a sandbox_escape rule, and both the code and the test say
it is a reviewer's signal rather than a control: the control is that the globals are
absent at run time, which the sandbox tests assert one by one.
Three corrections to the slice's own assumptions, all from measurement before code:
luerl_sandbox is a module of luerl rather than a package, lua_entry has existed in
the skills parser since slice 040, and AC1's reduction bound is not one this library
delivers.
mix gate: 794 passed (14 properties, 780 tests), 18 excluded.
Signed-off-by: Ayla Croft <aylacroft@proton.me>
Copy file name to clipboardExpand all lines: .sobelow-skips.reasons
+1Lines changed: 1 addition & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -14,3 +14,4 @@
14
14
5EFB666 Rekeyed at slice 013 (the moduledoc grew three lines for the macOS and Windows liveness check). Slice 010, Trinity.DataDir.Lock read/1: reads <data_dir>/LOCK, a path this module composes from the data directory and a constant file name; no caller supplies it.
15
15
7D2B826 Rekeyed at slice 013 (the moduledoc grew three lines for the macOS and Windows liveness check). Slice 010, Trinity.DataDir.Lock.release/2: removes <data_dir>/LOCK only when the file carries this process's own token; same composed path as the read.
16
16
42D2817 Rekeyed at slice 013 (the moduledoc grew three lines for the macOS and Windows liveness check). Slice 010, Trinity.DataDir.Lock take_over/2: rewrites <data_dir>/LOCK after the recorded holder's pid is found dead; same composed path, same constant name.
17
+
30481DE Slice 110, the skill script runner: the path sobelow cannot trace is the return of `Trinity.Sandbox.Skill.script_path/2`, which resolves it with `Path.expand/2` and refuses anything landing outside the skill's own `scripts/` directory. sobelow sees a function parameter and stops there; it cannot follow the containment into the previous call. The check is public precisely so it can be tested rather than trusted, and `test/trinity/sandbox/skill_test.exs` asserts the traversal case, the absolute-path case and the pathless-skill case against the real function rather than a copy of it. This is the only finding sobelow reports in the tree.
Copy file name to clipboardExpand all lines: README.md
+24-10Lines changed: 24 additions & 10 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -37,12 +37,13 @@ desktop application. Apache-2.0, developed in the open from the first commit.
37
37
38
38
## Status
39
39
40
-
Pre-alpha, and usable from source. Milestones M0 to M4 are approved, **M5a Automates** with them
41
-
(050, the scheduler; 059, the MCP measurement; 060, the MCP client; 061, the MCP server; 062, MCP
42
-
authorization), **M5b Reaches** in part (070, the gateway core; 080, subagents) and the first slice
43
-
of **M6 Ships** (090, observability and the cost ledger), with 002 (the supply chain slice) beside
44
-
them: 29 slices, each merged with a merge commit and tagged `slice/NNN`
45
-
(`git tag -l 'slice/*' | wc -l` → 30, on 2026-09-24: one more tag than slices, because
40
+
Pre-alpha, and usable from source. Milestones **M0** to **M4** are approved, **M5a Automates** with
41
+
them (050, the scheduler; 059, the MCP measurement; 060, the MCP client; 061, the MCP server; 062,
42
+
MCP authorization), **M5b Reaches** in part (070, the gateway core; 080, subagents), the first slice
43
+
of **M6 Ships** (090, observability and the cost ledger), **M7 Sandboxed** (110, executable skills in
44
+
an in-process sandbox), and the first slice of **M9 Donatable** (120, the open-source hygiene audit),
45
+
with 002 (the supply chain slice) beside them: 36 slices, each merged with a merge commit and tagged
46
+
`slice/NNN` (`git tag -l 'slice/*' | wc -l` → 37, on 2026-09-24: one more tag than slices, because
46
47
`slice/090` was pushed at the wrong commit before its merge landed and this repository's ruleset
47
48
forbids moving or deleting a tag; the correct one is `slice/090.1`, and `git tag -n20 slice/090.1`
48
49
says so in its own annotation rather than leaving a reader to work it out). What that means in
@@ -134,6 +135,16 @@ enforces it.
134
135
by model, by session, by persona, against budgets that warn and, when you ask them to, refuse.
135
136
`/activity` shows the live stream with the spend beside it; a LiveDashboard page lists the
136
137
running sessions, asking each process its state rather than trusting a row.
138
+
-**Computes, rather than estimates.** A `run_lua` tool runs a Lua script inside this process: no
139
+
operating system process, no filesystem handle, no socket. A model asked to total a column
140
+
produces a number that looks right; a script produces one that is right and leaves the script in
141
+
the transcript for anyone to check. The limits are the virtual machine's rather than a promise:
142
+
the runner sets its own heap ceiling and is killed at it, a wall clock bounds the run, and the
143
+
pool refuses an over-limit run by name instead of queueing it invisibly. What a script wants
144
+
*done* it asks for through `trinity.tool`, which is decided by the same permission gate and leaves
145
+
the same receipts as any other call, so there is no second door. A skill can ship a script of its
146
+
own, and where that script may read is checked by resolving the path rather than by trusting it.
147
+
`docs/sandbox.md` states what this does **not** protect against as plainly as what it does.
137
148
-**Runs on a schedule.** Tasks on the `/tasks` page: a prompt, a persona, the skills to hint,
138
149
and when (a cron expression, a one-shot time, or a phrase like "every weekday at 9am" the
139
150
model turns into cron). Each run is a fresh conversation you can open, its result waits on
@@ -144,8 +155,9 @@ enforces it.
144
155
145
156
Not there yet: the messaging platforms themselves (071 Telegram and 072 Discord, which need a bot
146
157
token the project does not yet hold), the native desktop shell and signed releases (the rest of M6),
147
-
executable skills in a sandbox (M7). The [Milestones](#milestones) section below sets out the order
148
-
the remaining work is being built in.
158
+
store-and-forward receipts for disconnected operation (026, waiting on an answer from the external
159
+
authority plane). The [Milestones](#milestones) section below sets out the order the remaining work
160
+
is being built in.
149
161
150
162
The interface is a local web page; the desktop shell exists as a packaging spike, not a product.
151
163
If you want to follow along, watch the roadmap and the tags.
@@ -244,7 +256,8 @@ rather than described by it.
244
256
| Auditability | Decisions and effects are recorded in an Ed25519-signed hash chain with checkpoints and a verifier, so an operator can reconstruct what was done, by whom and under what decision. |
245
257
| Untrusted content | Anything arriving from outside the machine is marked untrusted at the boundary and is never treated as instruction. |
246
258
| Approved cryptography | A dedicated CI leg builds from source and runs the cryptographic properties inside a FIPS-mode container, so statements about approved algorithms are measured on that leg rather than asserted. |
247
-
| Per-file licensing | Every one of the 609 files in this repository carries copyright and licence information, following the REUSE Specification 3.3. `reuse lint` exits 0 and runs in CI. What is not this project's says so: the desktop shell's icons are Tauri's template icons and are attributed to Tauri, not to us. |
259
+
| Exercised by generated input | The canonicalisers, the argument validator and the receipt chain are tested with property-based testing as well as by example, because code that answers "is this the same thing" fails on the spellings nobody thought of. The suite carries a planted defect it catches and the example tests miss, so a property that has never failed is not mistaken for one that cannot. |
260
+
| Per-file licensing | Every one of the 630 files in this repository carries copyright and licence information, following the REUSE Specification 3.3. `reuse lint` exits 0 and runs in CI. What is not this project's says so: the desktop shell's icons are Tauri's template icons and are attributed to Tauri, not to us. |
248
261
| Supply chain | Dependency and licence audits run on every commit; dependency versions are pinned in `VERSIONS.md` and verified against the lock file by the gate. A CycloneDX bill of materials is generated by the gate on every commit and ships beside every packaged binary, so whoever holds the artifact holds the list of what is in it; the bill states its own coverage inside the document, including what it does not cover. |
249
262
| Provenance | Every commit carries a Developer Certificate of Origin sign-off, enforced by a hook and independently by CI. Every packaged binary carries build provenance attested through the workflow's own identity and recorded in a public transparency log, verified in the same run that produced it, so a holder can check where the bytes were built without trusting this page. |
250
263
| Independent self-certification | The project holds the [OpenSSF Best Practices **silver** badge](https://www.bestpractices.dev/projects/14772) (awarded 2026-09-23), assessed against the Open Source Security Foundation's published criteria at two tiers. Every required criterion at both is met. What is not met is stated rather than stretched: at passing, three suggested criteria (semantic versioning, which begins at the first supported release, and two concerning dynamic analysis tooling the project does not run); at silver, a bus factor of two and an accessibility assessment, both suggested-tier, and signed version tags. |
@@ -266,7 +279,7 @@ rather than described by it.
266
279
| M7 Sandboxed | Executable skills in an in-VM sandbox | 110 approved |
M0 to M5a are approved as of 2026-09-22. Of M5b, 070 and 080 are approved as of 2026-09-23; 071 and 072 wait on a messaging bot token the project does not yet hold. Of M6, 090 is approved as of 2026-09-24. Slice numbers have gaps on purpose (000, 001, 010, 011 and so on) so that a slice can be inserted
282
+
M0 to M5a are approved as of 2026-09-22. Of M5b, 070 and 080 are approved as of 2026-09-23; 071 and 072 wait on a messaging bot token the project does not yet hold. Of M6, 090 is approved as of 2026-09-24. **M7** is approved as of 2026-09-24 (110). Of M9, 120 is approved as of 2026-09-24. Four slices merged on 2026-09-24 were not on the plan and are recorded as what they were: 004 (property-based testing, taken because it closes two OpenSSF gold criteria), 014 (a defect: a session past 500 messages built its prompt from the *first* 500), and 027, 028 and 029 from the connectome research. Slice numbers have gaps on purpose (000, 001, 010, 011 and so on) so that a slice can be inserted
270
283
later without renumbering anything.
271
284
272
285
**[`ROADMAP.md`](ROADMAP.md)** sets out what the project intends to do and what it intends not to
@@ -283,6 +296,7 @@ do over the next year, and why the order is what it is.
283
296
|`THIRD_PARTY_LICENSES.md`| Every dependency and its licence, generated from the CycloneDX bill. A dependency whose licence neither the bill nor a declared entry supplies fails the gate |
284
297
|`LICENSES/`| The full text of every licence the tree uses, as the REUSE Specification requires |
285
298
|`docs/effects-catalog.md`| The published effect catalogue: every tool that can cause an effect, its risk tier and effect class, with a version that is a hash of its own rows. Generated, and the gate fails when it and the tree disagree |
299
+
|`docs/sandbox.md`| What the Lua sandbox protects against, what it does not, and which of its limits are enforced by the virtual machine rather than sampled |
286
300
|`docs/telemetry.md`| The telemetry event catalogue: every event this tree emits, its measurements and metadata, and the rule about what never appears in one |
287
301
|`docs/adr/`| Architecture decision records. One is added whenever a decision changes |
288
302
|`docs/10-assurance-case.md`| The structured argument that the security claims hold, with the evidence for each and the assumptions and limits named |
0 commit comments