Skip to content

fix(opencode): prepare agent-owned sandbox scratch directory - #561

Merged
basiliskus merged 3 commits into
mainfrom
fix/tmp-opencode-ownership-acq-kit
Nov 4, 2026
Merged

basiliskus merged 3 commits into
mainfrom
fix/tmp-opencode-ownership-acq-kit

Conversation

@mogul

@mogul mogul commented Oct 31, 2026

Copy link
Copy Markdown
Contributor

Summary

Fix the OpenCode acq-kit scratch directory being owned by root. The install-time version checks run as root and can create /tmp/opencode as 0:0, mode 0755, leaving the agent unable to write there.

The kit now prepares the directory on every sandbox startup as 1000:1000, mode 0700. It rejects pre-existing symlinks and non-directories and does not recursively change existing contents.

Eligibility & Accountability

  • Human author confirms federal employee/contractor eligibility.
  • Human author confirms understanding and accountability.
  • Human author attests the right to release this work under CC0-1.0.

AI Assistance Disclosure

  • OpenCode AI agent implemented the scoped fix, regression tests, documentation, and verification. Two read-only adversarial AI reviews found no blockers in the final change.
  • AI-generated behavior was checked with real filesystem operations and the repository's validators and tests.
  • No third-party material was copied.

Type of Change

  • Bug fix
  • Documentation update
  • Infrastructure/tooling change

Motivation

The affected sandbox was observed to have /tmp/opencode owned by 0:0, mode 0755; agent writes failed with EACCES. Startup preparation also recreates the directory after /tmp is cleared. The updated kit must be applied to existing sandboxes; merely restarting OpenCode does not apply the fix.

Plan and Scope

User approved a scoped change to the OpenCode kit's spec.yaml, README.md, and regression tests. No dependency pins, provider configuration, isolation backend configuration, or CI configuration changed.

Testing / Verification Transcript

Commands ran with an isolated verification environment and private temporary files. Only summaries are included; no raw runtime or credential-bearing logs are attached.

Check Observed result
make validate PASS; pre-existing documentation-term advisories only
make validate-kits PASS, strict schema/registry validation
make validate-provider-input-contract PASS
make generate-check PASS; indexes remain current
make scan-shell PASS
python -m pytest scripts/tests/ -q PASS: 743 tests
make test-kits PASS: OpenCode 67, USAi kit 18, Paseo 79, USAi provider 153 tests
python -m ruff check scripts PASS
git diff --check PASS
Final adversarial AI review No blockers; focused new tests 5/5 passed

The five new tests cover numeric ownership/startup wiring, creation and repeat execution, permission repair with content preservation, symlink refusal, and regular-file refusal. They run the real shell command and install against a temporary path, substituting the caller's UID/GID so they work without root.

Initial verification was blocked by the running sandbox's unwritable scratch directory and missing Python tools. After installing the pinned project/dev tools locally, temporary fixtures placed inside the checkout interfered with repository-wide scanners. Moving fixtures to a user-approved private temporary directory resolved these environmental failures; all listed checks were rerun successfully.

Live root-to-agent ownership transfer and actual SBX/MSB startup dispatch are BLOCKED here: no root access or usable Docker daemon. Full make ci including network-backed dependency audit and pre-commit hooks was not run. CI and human review remain required before merge/deployment.

Safety Checklist

  • No secrets, API keys, tokens, or passwords included.
  • No real PII, CUI, customer data, internal hostnames, or private URLs included.
  • Tests use synthetic contents and isolated subprocess environments.
  • Conventional commit includes AI attribution.
  • No pattern changes; pattern-specific frontmatter/output-contract checklists are not applicable.

Security Impact

Root work is limited to the fixed scratch directory, with private rather than world-writable permissions. Existing symlinks and non-directories fail explicitly without replacement. Checks are not atomic protection against concurrent hostile path replacement; the documented scope is refusal of pre-existing symlinks. Existing root-owned contents are deliberately not recursively changed and may remain unwritable. No authentication, authorization flow, credential handling, network access, or exposed ports change.

Rollback

Revert this PR and reapply the previous kit. This stops subsequent startup repairs; it does not restore previous directory metadata automatically. No existing contents are deleted by this change.

Reviewer Notes

Please confirm the root startup hook executes before agent entry on both supported backends and verify that UID/GID 1000:1000 matches the sandbox agent. Human code-owner review is required; the AI agent has not approved or merged this PR.

Prepare /tmp/opencode on each kit startup with UID/GID 1000 and mode 0700. Refuse symlinks and non-directories, preserving existing contents. Add regression tests and lifecycle documentation.

Co-authored-by: OpenCode AI Agent <[email protected]>
@mogul
mogul requested a review from a team as a code owner October 31, 2026 19:27

@basiliskus basiliskus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Verdict: comment — 1337238a7e7fb93e46f3ef746727bbd0095c6fe9

No findings in the scoped scratch-directory change. Required CI is not green: Markdown Lint failed during the unchanged minimatch dependency audit, before linting. Live root ownership transfer and backend startup completion ordering remain unverified.

Assisted-by: OpenCode [GPT 6.1 Sol] skill:change-review

@basiliskus basiliskus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Verdict: request changes — 74412c6002975952fac9f7e88876acc8b71ffedb

The prior review had no code findings. Its CI gap is resolved: the current CI run passed against this head merged with base 73a3c9fde57065b2371cdddc54430fe98b7a4aa1.

Must fix before merge: Resolve scratch ownership from the actual sandbox agent instead of literal UID/GID 1000.

# Sev Where Problem → failure scenario Evidence
1 major integrations/isolation/acq-kits/opencode/spec.yaml:133 MSB supports OCI images where UID 1000 is occupied and provisions agent at another UID. This root command assigns /tmp/opencode to 1000 with mode 0700, leaving the actual agent unable to write there. Resolve ownership with id -u agent / id -g agent (or the equivalent by-name identity) and add coverage for a non-1000 agent; update the README's numeric-ownership claim. confirmed by consumer trace: Quickstart acq.backends/msb.sh:4318–4321,4354–4363 allocates a free UID; :2515–2517 maps kit user: "1000" to agent, but does not rewrite numeric arguments inside root shell commands.

Not verified: Live root-to-agent transfer and SBX/MSB startup execution were not exercised; local filesystem tests substitute the caller's UID/GID and cannot validate this identity mapping.

Assisted-by: OpenCode [GPT 6.1 Sol Latest Guardrails Defaultv2] skill:change-review

Look up the sandbox agent's uid/gid with id instead of assigning the
literal 1000:1000, which breaks on MSB images where acq provisions agent
at another uid. Fail closed when the agent user is missing. Add tests
with id/install shims for a non-1000 agent and a missing agent.

Co-authored-by: OpenCode Agent <[email protected]>
@mogul

mogul commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

🤖: AI agent (First Mate), posting on behalf of @mogul. Reply to the review at 74412c60.

Fixed in 9065e0cb. The root startup step no longer hardcodes 1000:1000: it resolves the sandbox agent with id -u agent and id -g agent, fails closed with a clear message if the user does not exist (before touching the filesystem), and then runs install -d -o "$agent_uid" -g "$agent_gid" -m 0700 /tmp/opencode. The symlink and non-directory refusals and the no-recursive-chown behavior are unchanged, and the README sentence about numeric ownership is updated.

tests/tmp-directory.test.mjs now uses id and install shims to cover a non-1000 agent (uid 2345 and gid 2346, asserting the exact install arguments) and a missing agent (fails closed, install is never called). The static test asserts the startup command contains no literal 1000, and the original five tests still pass.

The only other literal 1000 in spec.yaml is the user: "1000" field on the provider-config render step. That is a kit user field that acq maps to agent (Quickstart acq.backends/msb.sh), not a numeric argument inside a root shell command, so it is unchanged here.

Not verified: live execution inside a real SBX or MSB sandbox, and acq translation of the new command (the shims substitute the caller identity).

@basiliskus basiliskus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Verdict: approve — 9065e0cbf4ad34f190fc0117867747c7f0375011

Prior major finding resolved in 9065e0cb: integrations/isolation/acq-kits/opencode/spec.yaml:127–139 resolves ownership with id -u agent / id -g agent. The non-1000 and missing-agent regressions pass and fail against the prior hardcoded implementation for the intended reasons. The README now matches the implementation.

Must fix before merge: none. No new findings.

Nit: Update the PR body's remaining 1000:1000 claims and five-test summary to describe name-resolved ownership and seven tests.

Not verified: Live root-to-agent ownership transfer and actual SBX/MSB startup execution; local tests use identity/ownership shims. CI passed, but its raw checkout log names a merge with f0a951f363f26031a28faaafff70ab116b14cde2, not the currently reported PR base 73a3c9fde57065b2371cdddc54430fe98b7a4aa1.

Assisted-by: OpenCode [GPT 6.1 Sol Latest Guardrails Defaultv2] skill:change-review

@basiliskus
basiliskus added this pull request to the merge queue Nov 4, 2026
Merged via the queue into main with commit 89a66b2 Nov 4, 2026
7 checks passed
@basiliskus
basiliskus deleted the fix/tmp-opencode-ownership-acq-kit branch November 4, 2026 23:17
mogul pushed a commit that referenced this pull request Nov 5, 2026
🤖 I have created a release *beep* *boop*
---


##
[1.19.0](v1.18.0...v1.19.0)
(2026-11-05)


### Features

* **oci-engine:** qualify bounded rootless resource controls
([#562](#562))
([cf6d7a5](cf6d7a5))
* **opencode:** add live image-capability verification script (not yet
run)
([#572](#572))
([f0a951f](f0a951f))
* **paseo:** bump pinned Paseo CLI to 0.15.0
([#560](#560))
([0c4e1a8](0c4e1a8))
* **usai-catalog:** carry verified capability metadata through a catalog
refresh
([#578](#578))
([fcd94e5](fcd94e5))
* **usai-catalog:** publish verified image input modality for 12 USAi
models
([#575](#575))
([907d486](907d486))
* **usai-catalog:** single refresh entrypoint for the catalog and
neutral artifacts
([#573](#573))
([0c1b145](0c1b145))


### Bug Fixes

* **ci:** validate pending release comparisons against their commit
([#570](#570))
([0961019](0961019))
* **deps:** update Ruff package and hook together to 0.17.3
([#568](#568))
([a6266dd](a6266dd))
* **opencode:** prepare agent-owned sandbox scratch directory
([#561](#561))
([89a66b2](89a66b2))
* **paseo:** launch real opencode outside PID 1
([#559](#559))
([ce4aa08](ce4aa08))
* **paseo:** verify runtime identity without false orphan verdicts
([#567](#567))
([1062244](1062244))
* **usai-catalog:** allow summary and title roles in catalog
recommendations schema
([#551](#551))
([ad2aadd](ad2aadd))
* **usai:** retire stale references to the removed model sync script
([#552](#552))
([95be9b9](95be9b9))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants