Skip to content

fix: harden workload context delivery - #516

Merged
pdettori merged 1 commit into
rossoctl:mainfrom
moonlight16:fix/workloads-context-followups
Oct 14, 2026
Merged

pdettori merged 1 commit into
rossoctl:mainfrom
moonlight16:fix/workloads-context-followups

Conversation

@moonlight16

Copy link
Copy Markdown
Member

Follow-up to #494 (run tasks on a workload's context in shared sandboxes), from Paolo's review.

  • Archive limits: Moca refuses a bundle that unpacks past 1.25 GiB (Context Service's 1 GiB of files, plus headroom for tar headers), and the sandbox refuses more than 10,000 files before unpacking. Refused bundles are never cached.
  • Memory budget: concurrent downloads share a 512 MiB budget (MOCA_CONTEXT_DOWNLOAD_BUDGET_BYTES).
  • Unguessable cache names: sandbox cache folders are named with an HMAC of the owner under a per-process secret, so an agent can't plant a copy for another user's folder ahead of time.
  • Error codes: any async run naming a workload answers 501 workload_runs_unavailable.
  • Docs: the API spec lists 500 workloads_misconfigured. The docs and comments say the sandbox's sha256 check proves transport integrity only, and that per-owner cache folders aren't isolation, since every agent runs as the same Linux user.
  • Tests: they assert error bodies.
  • Cache eviction: deleting a workload evicts its cached download.

Isolation between users remains #476 (phase 2: per-workload sandboxes and storage).

🤖 Generated with Claude Code

Follow-up to rossoctl#494 from review:
- Refuse bundles that inflate past 1.25 GiB, and more than 10,000 entries in the sandbox before extracting.
- Share a 512 MiB in-flight download budget across concurrent downloads.
- Name sandbox cache folders with an HMAC of the owner under a per-process secret.
- Answer 501 workload_runs_unavailable for any async run naming a workload.
- Document that the sandbox sha256 check is transport integrity only; list 500 workloads_misconfigured.
- Assert error bodies in tests; evict a deleted workload's cached download.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Jeremy Cohn <Jeremy.Cohn@ibm.com>

@ingpaolodettori-dev ingpaolodettori-dev left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this closes the #494 follow-ups cleanly. The budget reserve/release paths, the inflate cap, and the async-refusal reordering look right and are well tested. Three suggestions, none blocking: (1) the default cache secret is per forked worker, so a W-worker supervisor splits each user's sandbox cache W ways; (2) the download budget is released before the buffer is delivered to the sandbox, so it doesn't bound resident bundles; (3) please confirm the 10k entry cap counts the same thing as Context Service's limit (tar -t counts directories too). Plus one docs nit about the require-auth 401 ordering.

Author: moonlight16 (MEMBER — maintainer) · Agent/IDE config: none · Commits: 1, signed-off · CI: passing

* process: replicas with different secrets only miss each other's copies.
*/
export function sandboxCacheScope(owner: string): string {
cacheSecret ??= process.env.MOCA_CONTEXT_CACHE_SECRET?.trim() || randomBytes(32);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: The random-per-process default is per worker, not per replica. The supervisor forks SH_WORKERS (default availableParallelism()) processes, each with its own randomBytes(32), and routes leastInFlight. So by default one user's revision gets a different cache name on each worker: runs landing on different workers re-transfer and re-extract the bundle onto the shared sandbox disk, up to W copies when RETAIN_MINUTES>0. Every worker restart also orphans the cached copies. Suggest a stable default that agents still never see, e.g. derive it from CONTEXT_SERVICE_TOKEN (HMAC(token, 'moca-context-cache-scope'); it is already required whenever workloads run), or have the supervisor generate one and pass it to its forks. At minimum, fix the comment at L86 and the docs table ("random per process" → per worker process, set it for W>1).

await assertCompleteGzip(bundle, maxExpanded);
return bundle;
} finally {
release();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: The reservation is released when the download finishes, but loadBundle's Buffer then lives on through the whole relay delivery (8 MiB SandboxExec chunks plus the sandbox sha256 check), which is usually the slow part. So the budget bounds download buffering, not what's resident: N concurrent runs of distinct large bundles (different users or revisions; same-revision runs already share a download) still hold N buffers during delivery. Either hold the reservation until overlayContext finishes with the bytes (e.g. return the release with the buffer and release after the populate step), or narrow the comment at L257 and the docs ("Downloads in flight share a byte budget") so they don't promise a memory bound.

`STAGE=${sq(`${BASE}/cache/.stage.${rev}.${sid}`)}`,
`trap 'chmod -R u+w "$STAGE" 2>/dev/null || true; rm -rf "$STAGE" "$ARCHIVE"' EXIT`,
`printf '%s %s\\n' ${sq(sha256)} "$ARCHIVE" | sha256sum -c --status`,
`ENTRIES="$(tar -tzf "$ARCHIVE" | wc -l)"; [ "$ENTRIES" -le ${maxEntries} ] || { echo "context bundle has $ENTRIES entries, over ${maxEntries}" >&2; exit 1; }`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: tar -tzf | wc -l counts every member, directories (and a ./ root, if the export has one) included. If Context Service's 10,000 limit counts files, a bundle it accepted with 10,000 files in a few dirs would be refused here, which breaks the "a bundle Context Service accepted is never refused" principle the expanded cap follows. Please confirm what Context Service's limit counts and what its export contains. If the limit counts files, count regular files only (tar -tvzf … | grep -c '^-') or give the default headroom as the expanded cap does. Also, the PR body says "more than 10,000 files", but the code counts entries.

Comment thread docs/context-service.md
routes are off, answer `501 workload_runs_unavailable`. The sandbox tier is the session's, chosen as
for any other run.
`409 workload_not_ready`, and another kind of run `400 workload_run_requires_prompt`. A synchronous
run with no token is `401 token_required`. Asynchronous runs on a workload, with or without a token,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: "with or without a token" holds only when SH_REQUIRE_AUTH is off. On a require-auth deployment, resolveTurnAuth answers a token-less async request with 401 token_required before the new check runs. The same applies to openapi L469 "whether or not it carries a token" and leaf-and-envelope.md. Suggest qualifying it as "(a token-less caller on a require-auth deployment still gets 401 first)".

@pdettori
pdettori merged commit 8f60b86 into rossoctl:main Oct 14, 2026
15 checks passed
moonlight16 added a commit that referenced this pull request Oct 14, 2026
Move the context workflows list to docs/concepts/ with a Preview line and index entries, use the MOCA spelling, state phase 1 limits and that shared sandboxes are not a security boundary, link Context Service's capture/restore for workflow 9, and say "workflow N" for cross-references. Also correct that under SH_REQUIRE_AUTH an unauthenticated workload run is 401 token_required first (missed #516).

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Jeremy Cohn <Jeremy.Cohn@ibm.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants