Pantry Runner¶
The Pantry Runner is a GitHub Actions workflow repository that tests Jarvis command submissions in an isolated sandbox before they are published to the Pantry. It is dispatched by jarvis-pantry-store via workflow_dispatch and posts HMAC-signed results back via HTTP callback.
Quick Reference¶
| Source | jarvis-pantry-runner/ |
| Runtime | GitHub Actions (public repo) |
| Workflow | .github/workflows/container-test.yml |
| Dispatcher | jarvis-pantry-store via workflow_dispatch |
| Callback | HMAC-SHA256 signed POST to pantry store |
Workflow Structure¶
The container-test.yml workflow uses a two-job split to ensure submitted code can never access the callback signing key.
Job 1: test (no secrets)¶
- Has
permissions: {}— no access toGITHUB_TOKENor thepantry-callbackenvironment - Clones the submission repository
- Pre-stage: pulls
python:3.11-slim, runspip install(network enabled) for SDK + submission deps into a named Docker volume - Harness run: executes the SDK test harness inside a sandboxed container with strict resource constraints (see Sandbox Security Model)
- Uploads the harness JSON result as a build artifact
Submitted code runs only in this job. It cannot reach external services and has no access to any secrets.
Job 2: callback (needs: test, if: always())¶
- Runs in the
pantry-callbackGHA environment (wherePANTRY_CALLBACK_SIGNING_KEYlives) - Downloads the harness artifact from Job 1
- Signs the result with HMAC-SHA256
- POSTs the signed payload to
jarvis-pantry-storeat/v1/submissions/{id}/container-result
This job never clones or runs the submission. The signing key is only visible here, not in the test job.
Sandbox Security Model¶
The harness container (Job 1) runs with strict Docker constraints:
| Constraint | Effect |
|---|---|
--network=none |
No outbound network access during test execution |
--read-only |
Filesystem is read-only (only /output is writable via tmpfs) |
--memory=128m |
Prevents resource exhaustion |
| Submission bind-mounted read-only | Submitted code cannot modify itself at runtime |
/tmp on tmpfs |
Ephemeral scratch space, not persisted |
The pre-stage step (dependency install) runs with network access, but in a separate throwaway container — not the harness sandbox. Deps are written to a named Docker volume that the sandboxed container then mounts read-only.
On sdist packages: the pre-stage pip install does not restrict to binary wheels (--only-binary=:all: is not set), so submissions whose dependency tree includes sdist-only packages (e.g. pydora → blowfish==0.6.1) install correctly. The pre-stage container is throwaway and holds no Pantry secrets — the callback signing key is gated to the separate callback job. A malicious build hook's worst-case outcome is faking a passing test; it cannot exfiltrate secrets or reach external services from the harness sandbox (--network=none --read-only).
Signing Model¶
The runner signs the callback body using HMAC-SHA256:
digest = HMAC-SHA256(
key = PANTRY_CALLBACK_SIGNING_KEY,
msg = "{submission_id}|{nonce}|{body_bytes}"
)
The digest is sent in the X-Pantry-HMAC header. The pantry server recomputes the digest from the raw request bytes (not from parsed JSON) to verify authenticity.
The nonce is generated by the pantry store at dispatch time and passed as a workflow_dispatch input. It binds the callback to a specific submission and prevents replay attacks.
Required Secrets & Environments¶
| Secret | Location | Description |
|---|---|---|
PANTRY_CALLBACK_SIGNING_KEY |
pantry-callback GHA environment |
Shared HMAC key. Must match the Fly secret on jarvis-pantry-store. |
See Callback Signing Key Rotation for rotation instructions.
Workflow Inputs (set by pantry store at dispatch time)¶
| Input | Description |
|---|---|
submission_id |
UUID of the submission |
nonce |
Per-submission random value for replay protection |
repo_url |
Git URL of the submission repository |
ref |
Git ref to test |
packages |
Resolved pip lockfile content |
sdk_ref |
jarvis-command-sdk git ref to install in the pre-stage |
Troubleshooting¶
harness produced no output / harness did not write JSON¶
This error typically means the pre-stage pip install failed before the harness container started. Check the Pre-stage step in the GHA log:
- Syntax error in pre-stage script: an apostrophe or unbalanced quote inside the
sh -c '...'string closes the outer shell string early. Look forsyntax error near unexpected tokenin the step output. - Pip install failure for sdist package: if a build hook fails (e.g. missing system library), the log will show the actual pip error above the misleading envelope message.
gitnot found: the pre-stage container ispython:3.11-slimand includesgitfor SDK installation. If this error appears, the pre-stage image definition changed.