Repository navigation
Named lab actions, and pairing scopes so a lab page cannot run arbitrary code - #2
Merged
Merged
Conversation
…ary code
A course page that only runs a lab's prepared commands no longer needs a token that can run any
Python on the learner's machine.
Pairing scopes
- POST /pairing/request takes {"scope": "code" | "actions"}. The default stays "code", so
existing clients keep working. The terminal prompt says what the scope allows.
- "actions" tokens get 403 from /kernel and /cell; require_auth now means code scope, and
require_lab_auth accepts both. Pairings saved before scopes existed, loopback origins and
--no-pairing count as "code".
Named lab actions (labs.py, api/labs.py)
- A lab repository declares actions in lab.toml (schema 1): argv steps run without a shell,
with a label, description and timeout. Unknown keys and anything out of range are errors.
- POST /labs/prepare {repo, commit}:
- accepts https URLs only (GIT_ALLOW_PROTOCOL), no credentials, full SHA;
- fetches exactly that commit (--depth 1, no submodules, no credential prompt) into
<runtime dir>/labs/<repo>/<sha[:12]>;
- reads lab.toml;
- once per origin + repo + commit + manifest hash, prompts in the terminal with every
command, then creates the lab's venv.
- POST /labs/runs starts an action, one run at a time per lab. Poll GET /labs/runs/{id}?offset
for status and new output; POST .../stop sends SIGINT to the process group, then SIGKILL.
GET /labs/runs lists this origin's runs; a run is visible only to the origin that started it.
Output is kept to the last 1M characters, and shutdown stops and awaits running actions.
- runtime/info advertises lab_actions and pairing_scopes.
Verified end to end on Linux:
- The branch runtime ran with pairing required, against SciMigo/restate-durable-agent-demo
@ e7bdf44 (branch lab-toml, which adds its lab.toml).
- Pairing as https://scimigo.com with scope "actions" worked, and /cell/run returned 403.
- Prepare asked for approval once, listing all nine actions' commands, in 14.5 s; the second
prepare did not ask.
- setup ran docker compose up and pip install. 1.1 gave "model calls: 3 weather calls: 1",
the same as the recorded run, and teardown removed the container.
Tests: tests/test_labs.py covers the manifest rules, source rules, fetching a commit from a
file:// repo (test-only protocol) and reusing it, approval once and a declined approval, and
runs: success, a failing step ending the action, stop, one at a time, timeout, and other
origins not seeing a run. It also covers scope enforcement and the scope in pairing requests.
167 passed; ruff, format and mypy clean.
Docs: protocol.md (scopes, lab actions, lab.toml), integration.md (the flow for a lab page),
security.md (what approving a lab does and does not mean: it pins which commands and which
code run, it does not sandbox them), README, CLAUDE.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Lets a course page run a lab's declared commands, and nothing else, on the learner's machine.
Why
Today a paired site can run any Python through
/cell/run. A lab page only needs to start a few prepared commands, such asdocker compose upandpython demo.py …. With this PR, a site paired with the newactionsscope can only do that. If the page were compromised, its token could run only commands the learner already approved.Pairing scopes
POST /pairing/requesttakes{"scope": "code" | "actions"}. The default iscode, which is today's behaviour, so existing clients are unaffected.Access: actions - this site could run only the named actions of labs you approve one by one.actionstokens get 403 from/kernel/*and/cell/*.--no-pairingall count ascode.Named lab actions
lab.tomlin the lab repo (schema 1) declares[actions.<name>]entries. Each has alabel, adescription, atimeout, andstepsgiven as argv lists, run without a shell. Unknown keys are errors.POST /labs/prepare {repo, commit}:httpsURLs only (GIT_ALLOW_PROTOCOL, so nossh,fileorext::), without credentials, and a full 40-character SHA;--depth 1, no submodules, never prompts for credentials) and readslab.toml;POST /labs/runsstarts one, one at a time per lab.GET /labs/runs/{id}?offset=returns status plus new output.POST /labs/runs/{id}/stopsends SIGINT to the process group, then SIGKILL.GET /labs/runslists runs, so a reloaded page can find one still going.The docs cover all of this (
protocol.md,integration.md,security.md, README). The security doc states the limit plainly: approving a lab pins which commands and which code run, down to the commit; it does not sandbox them.Verified end to end (Linux)
This branch's runtime ran with pairing required, against SciMigo/restate-durable-agent-demo @ e7bdf44 (its
lab-tomlbranch):https://scimigo.comwith scopeactions;/cell/runreturned 403;/labs/prepareshowed the approval prompt with all nine actions' commands, and was approved once, in 14.5 s; the second prepare didn't prompt;setuprandocker compose upandpip install; 1.1 →model calls: 3 weather calls: 1, the same as the recorded run;teardownremoved the container.Tests
tests/test_labs.pyhas 35 tests:file://repo (a protocol allowed only in tests) and reusing it;167 passed; ruff, format and strict mypy are clean.
After this merges, the demo repo's
lab-tomlbranch merges too, and then scimigo.com's Restate lab page gets its run panel.🤖 Generated with Claude Code