glas-harness/workplans/GLAS-WP-0005-sandbox-boundary-runtime-hardening.md
tegwick 63a7f9f160
Some checks failed
ci / validate (push) Has been cancelled
fix: execute local reins through the sandbox owner
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0726e-5232-73f2-aaca-2c05ceb62efb
2026-09-05 19:16:22 +02:00

206 lines
9.5 KiB
Markdown

---
id: GLAS-WP-0005
type: workplan
title: "Sandbox-boundary correction and runtime hardening"
domain: infotech
repo: glas-harness
status: finished
owner: codex
topic_slug: sandbox-boundary-runtime-hardening
created: "2026-08-21"
updated: "2026-09-05"
state_hub_workstream_id: "c8049700-73ca-549a-b250-1020de1cddc5"
---
# Sandbox-boundary correction and runtime hardening
## Context
The versioned profile path provisions and tears down sand-boxer instances, but
the concrete rein adapters currently prefer the caller's original repository
path and invoke their CLIs as ordinary local subprocesses. They do not enter a
same-host bwrap namespace or cross the SSH reachability boundary for a remote
sandbox. As a result, the existing lifecycle evidence does not prove that the
agent process executed inside the selected sandbox, and the enabled remote
profile is not executable through its declared transport.
The review also found persistent task-spec files in `/tmp`, no outer subprocess
timeout, an undeclared direct `httpx` dependency, and a Forgejo workflow that
only prints routing probes rather than running tests or catalog validation.
## Goal
Make every governed execution use the workspace and execution transport
declared by the resolved sand-boxer reachability descriptor, fail closed when
that descriptor is unsupported, and close the adjacent runtime/CI gaps without
changing scheduling, rein credential ownership, or sand-boxer provisioning
ownership.
## Task: Correct workspace and execution transport selection
```task
id: GLAS-WP-0005-T01
status: done
priority: high
state_hub_task_id: "fc29c99f-6938-5450-868e-1a94123995f4"
```
Introduce one validated execution transport derived from `SandboxHandle`:
- same-host bwrap uses `workspace_dir` and enters the namespace identified by
`pid` for the rein subprocess;
- remote sandboxes use `remote_dir` and the declared SSH endpoint;
- the caller's source checkout is never passed to the rein as its execution
workspace after sandbox creation; and
- missing, ambiguous, or unsupported reachability fails closed before the rein
subprocess starts.
Add tests proving the original checkout is not selected, the expected transport
argv is used, remote execution is represented honestly, and teardown still
runs on every failure.
**Completed 2026-08-21:** added strict reachability-derived execution
transports. Local descriptors require `pid` + `workspace_dir` and wrap commands
with `nsenter`; remote descriptors require `ssh` + `remote_dir` and wrap
commands over SSH. Mixed/incomplete descriptors fail closed. The gateway no
longer passes the source checkout to a rein after provisioning, and tests cover
transport argv, workspace selection, refusal, cleanup, and teardown.
## Task: Bound subprocesses and remove temporary task material
```task
id: GLAS-WP-0005-T02
status: done
priority: high
state_hub_task_id: "219ecaeb-c576-5b93-b65c-6445ebd3d139"
```
Enforce the profile timeout on the outer rein subprocess, normalize timeout
failures, and remove generated task-spec files on success, ordinary failure,
timeout, and interruption. Caller-supplied task files must not be deleted.
Record and safely remove already-orphaned `glas-harness-task-*.json` files after
their exact scope is verified.
**Completed 2026-08-21:** the profile timeout now bounds the outer transport
subprocess. Generated task specs are mode-0600 files inside the sandbox
workspace and the gateway calls rein cleanup before sandbox teardown on every
terminal path; caller-owned task files are preserved. Verified all 22 legacy
`/tmp/glas-harness-task-*.json` files were owned by the current user, mode 0600,
and valid Glas task specs with no active rein process, then securely removed
those exact files.
## Task: Repair package dependency metadata
```task
id: GLAS-WP-0005-T03
status: done
priority: medium
state_hub_task_id: "0c57d3a4-40d4-5764-a0df-7c03c58fa23e"
```
Declare direct runtime dependencies directly, build the wheel, install it in an
isolated environment with the sandbox extra, and validate that packaged profile
and rein data resolve without relying on the source checkout.
**Completed 2026-08-21:** declared `httpx>=0.27` directly. Built the wheel with
uv, installed it outside the checkout, resolved all three profiles from packaged
data, verified both rein descriptors are present, and inspected wheel metadata
for direct and optional dependencies.
## Task: Replace routing-only CI with executable validation
```task
id: GLAS-WP-0005-T04
status: done
priority: high
state_hub_task_id: "222d9df6-5361-5b5e-8236-8fa3a1fbe3bf"
```
Make Forgejo CI check out the repository, install the project and its test
dependencies, run the full unit suite, and run `glas-harness profiles`. Retain
only routing probes that still provide operational value.
**Completed 2026-08-21:** replaced the two echo-only jobs with the estate's
archive-checkout pattern, explicit Python/sand-boxer/project installation, the
full test suite, and packaged catalog validation. Run `#45` was superseded and
canceled; run `#46` exposed five tests that incorrectly relied on the host's
global Git author identity. The same job reproduced this cleanly in the runner's
`node:20-bookworm` image, and the test repositories now configure a local,
non-deliverable test identity. Forgejo Actions run `#47` passed on published
commit `994b2da`.
## Task: Re-prove the sandbox boundary and close evidence
```task
id: GLAS-WP-0005-T05
status: done
priority: high
state_hub_task_id: "a4be4d68-b0af-5273-978a-367bba414f0c"
```
Run offline boundary tests and, where the already-approved local runtime permits
it, a bounded non-secret bwrap proof showing that execution targets the sandbox
copy and cannot mutate the source checkout. Correct prior evidence claims rather
than silently treating lifecycle-only proof as isolation proof. Run the full
suite and catalog validation, synchronize work records, and hand off any live
residual before finishing the workplan.
**2026-08-21 boundary recheck:** offline transport/package tests pass and the
prior GLAS-WP-0004 evidence now explicitly withdraws its isolation implication.
A real managed-agent bwrap probe failed closed: direct `nsenter` was denied on
the reported namespace and neither rein command is mounted inside the bwrap
root. The source checkout is no longer used as a fallback. Runtime mounting,
owner-exec, network, and credential delivery are handed off as residual
`GLAS-IN-0002`; live positive proof waits on that owner contract.
**2026-08-22 coordination:** primary registrar reconciliation assigned the
workplan, all tasks, and `GLAS-IN-0002` authoritative State Hub identities. A
fresh owner check found no sand-boxer implementation or returned contract, so
the registered residual was re-sent to sand-boxer in message
`2e203700-6a2c-4d86-8eed-5dea72e69994`. T05 remains `wait`; no unsafe host
fallback was introduced.
**2026-08-23 production evidence:** rein-aharness deployed the corrected
`worker_id`/`actor` mapping and ran ops run
`ededc939-266f-473c-8386-ffd3f027f5f0` with governed actor `agt`. Glas resolved
the local profile, created sandbox `d750cd5c`, and failed closed at
`session_start` before dispatch or commit because consumer `nsenter` could not
join the bwrap namespaces. The sandbox reached `destroyed` and its workspace is
absent. Loading Ubuntu's bwrap user-namespace AppArmor profile kept bwrap alive
with child `CapEff=0`, but did not make direct namespace entry permissible.
This confirms that T05 needs sand-boxer-owned execution rather than another
consumer-side namespace workaround.
**Completed 2026-09-05:** reviewed sand-boxer `b6655d8` and replaced the stale
T01 direct-nsenter implementation with sandbox-owner execution. Create and exec
now preserve exact actor/project and resolved request id as `consumer.run_id`;
every command carries the profile's value-free credential route references and
a timeout capped by its limit. Task writes and removal both execute inside the
sandbox, with prompt content supplied only over bounded stdin. Unbound local
descriptors, owner refusals, timeouts, and truncated output fail closed.
The real non-secret gateway proof passed in sandbox `e3907e55`: source checkout
absent and mutation refused, a new commit only in the sandbox copy, exact child
identity, mode-0600 task removed before teardown, loopback-only networking,
unchanged host source, and destroyed/absent sandbox workspace. Full suite:
**81 passed**; catalog validation passes. Reproduction and limitations:
[owner boundary evidence](../docs/evidence/GLAS-WP-0005-owner-boundary-2026-09-05.md).
The production runtime/credential/explicit-egress requirements remain live in
registered residual `GLAS-IN-0002` (`01a02b76-f020-7d60-a3ce-12a34c13ebce`),
refreshed before closure with owner responsibilities and real-rein acceptance.
Both local profiles remain blocked and the remote profile unverified. This
closes T05's bounded boundary proof and residual handoff; it does not certify
production rein readiness. Decision: `6d927f9f-d37d-4234-ad97-bad562b31eaf`.
## Overall acceptance
1. No governed rein invocation operates on the caller's original checkout.
2. The selected process crosses the declared local namespace or remote SSH
boundary, or fails closed without execution.
3. Profile timeouts bound the outer subprocess and generated task files do not
survive any terminal path.
4. Package metadata names every direct dependency and the built artifact carries
the executable profile/rein catalog.
5. Forgejo CI executes tests plus catalog validation.
6. Evidence distinguishes sandbox lifecycle from verified sandbox execution.