You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
chore(isolation): host-materialized provider facts (split from #436, blocked on quickstart#504) #456
Split out of #436, which removed a guest-written provider-facts file rather than ship it at the wrong trust level.
What was removed and why
acq-kits/usai-provider/spec.yaml wrote /var/lib/acq/models/providers/usai.json from inside the sandbox at startup as root. The rationale in the spec was that a root-owned guest path is not agent-writable. That is DAC-true and false as a security property: acq itself grants the agent passwordless sudo —
— and docs/BACKEND_GUIDE.md states the same guarantee as the sbx base-image contract. So sudo tee defeats it.
That matters more than a generic integrity concern because the facts file advertises keyEnv, a credential pointer. An agent able to rewrite it can point a consumer at a different env var. Confused-deputy primitive, shipping with a comment telling consumers to trust it.
What this issue needs
A host-materialized, read-only provider-facts source, per ADR-0004 (#423):
acq validates facts host-side before materializing — providerId as a safe whole-string slug, unique and matching its directory; keyEnv ownership (a kit may only name a credential it declares); modelsUrlhttps:// with host equal to the declared host and on the effective caps.network.allow union.
Materialized per-sandbox and mounted :ro at /var/lib/acq/host/models/providers/<providerId>/.
The :ro host-config mount does not exist on quickstart main. It lives on the unmerged branch origin/docs/adr-host-authoritative-config (GSA-TTS/agentic-coding-quickstart#504). Measured, not assumed — I had a subagent trace the provisioning path.
Also relevant, measured in the same pass:
Kit phases are a closed allowlist (install|initFiles|startup) and all three run in the guest. There is no host phase a kit can declare, so this cannot be solved kit-side.
Kit files[] are copied verbatim; no templating step exists.
acqdoes already generate kits/scripts host-side pre-create (twice: the sbx git-identity kit and the msb startup script), so the generate-and-inject shape exists to build on.
Not in scope here
Reconciling acq-neutral-model-catalog/v1 (introduced by ADR-0004) with the already-shipped usai-model-catalog/v1 in integrations/providers/usai/. Two catalog schemas coexist; tracked at #435, and it should land before a consumer depends on either.
AI-assisted (OpenCode), filed on operator instruction. Advisory — the design decision is a maintainer call.
Prior art worth weighing before this is built — Docker's Sandbox Kit Spec v3 (docker/sandbox-kit-spec, Apache-2.0, experimental, final targeted Q4 2026) solves the exact problem this issue exists for, and solves it differently from how this issue is currently specified.
Why it is relevant here
This issue inherits #436's framing: a facts file advertising keyEnv, the NAME of the env var holding the credential, materialized somewhere a consumer can trust. The trust problem was the location of the file.
Docker's credential@1 capability makes the location question mostly disappear, because the credential never enters the guest at all:
MUST NOT place the real secret in the container when proxyManaged or OAuth sentinels are in play: where the Kit names a variable or file, the container sees sentinel values there, and the boundary (proxy) substitutes the real credential on outbound requests matching the inject rules or resourceHosts.
MUST resolve the credential from the host-side store keyed by service. Host environment variables never auto-inject; a Kit cannot name where a secret lives, only what it needs.
The container gets GH_TOKEN set to a sentinel, deliberately non-empty "so Kit content can detect wiring without seeing the secret". An inject-only credential (no name) gets no environment presence at all.
Two further properties we have no equivalent for:
Phase-scoped with revocation."an install-only credential is injectable only while install hooks run and is revoked before the workload's entrypoint starts".
Cross-validated against the network policy.inject[].domain"MUST appear in the network policy's allow list for every declared phase" — because "an inject domain outside the allow list would be a credential mapped onto a connection that can never occur."
What this would change about this issue
If that model is adopted, keyEnv stops being a trust-sensitive field. Today an agent that rewrites the facts file redirects a consumer to a different env var and harvests whatever is there — which is why #436's version had to be removed. Under the sentinel model there is nothing in the guest to harvest, so the facts file's integrity matters much less and the host-side :ro materialization this issue is blocked on becomes a smaller requirement rather than the whole security control.
I am not proposing we adopt it. Three honest reasons to be cautious:
It requires a credential-substituting egress proxy. acq has no such component, and building one is far larger than this issue.
The spec is explicitly experimental and still moving.
It is OCI/BuildKit-coupled in ways acq's backend-neutral design (sbx and msb) is not.
But it is the structurally correct answer to the problem, and this issue should at least record that keyEnv-in-the-guest is a weaker design that was chosen for buildability, not because it is the right shape.
One correction to my own earlier framing
I initially read this as "acq is on a deprecated kit line that should migrate to v3." That was wrong, and measuring it inverted the conclusion:
v3 is not a schemaVersion bump — it is a different artifact format (an OCI image carrying declarations in a vnd.docker.sandbox.kit.descriptor manifest annotation). sbx v0.46.0 rejects "3" outright. So there is no migration available today and nothing here is urgent on that basis. The separate, real risk — that nothing checks whether the grammar acq does emit is still accepted — is filed as GSA-TTS/agentic-coding-quickstart#529.
Smaller borrowable detail
Docker's lifecycle@1files[] entries carry nouser: field at all, with the rationale: "A path only root can own is an install hook's job — what install[].user is for." That is a cleaner statement of the rule #436 violated, and it is enforced by the grammar's shape rather than by review.
AI-assisted (OpenCode), on operator instruction. Research summary — treat the quoted spec text as the authority, not my paraphrase.
Activity
wz-gsa commentedon Oct 1, 2026
Prior art worth weighing before this is built — Docker's Sandbox Kit Spec v3 (
docker/sandbox-kit-spec, Apache-2.0, experimental, final targeted Q4 2026) solves the exact problem this issue exists for, and solves it differently from how this issue is currently specified.Why it is relevant here
This issue inherits #436's framing: a facts file advertising
keyEnv, the NAME of the env var holding the credential, materialized somewhere a consumer can trust. The trust problem was the location of the file.Docker's
credential@1capability makes the location question mostly disappear, because the credential never enters the guest at all:A declaration looks like this:
The container gets
GH_TOKENset to a sentinel, deliberately non-empty "so Kit content can detect wiring without seeing the secret". An inject-only credential (noname) gets no environment presence at all.Two further properties we have no equivalent for:
inject[].domain"MUST appear in the network policy's allow list for every declared phase" — because "an inject domain outside the allow list would be a credential mapped onto a connection that can never occur."What this would change about this issue
If that model is adopted,
keyEnvstops being a trust-sensitive field. Today an agent that rewrites the facts file redirects a consumer to a different env var and harvests whatever is there — which is why #436's version had to be removed. Under the sentinel model there is nothing in the guest to harvest, so the facts file's integrity matters much less and the host-side:romaterialization this issue is blocked on becomes a smaller requirement rather than the whole security control.I am not proposing we adopt it. Three honest reasons to be cautious:
But it is the structurally correct answer to the problem, and this issue should at least record that
keyEnv-in-the-guest is a weaker design that was chosen for buildability, not because it is the right shape.One correction to my own earlier framing
I initially read this as "acq is on a deprecated kit line that should migrate to v3." That was wrong, and measuring it inverted the conclusion:
v3 is not a
schemaVersionbump — it is a different artifact format (an OCI image carrying declarations in avnd.docker.sandbox.kit.descriptormanifest annotation).sbxv0.46.0 rejects"3"outright. So there is no migration available today and nothing here is urgent on that basis. The separate, real risk — that nothing checks whether the grammar acq does emit is still accepted — is filed as GSA-TTS/agentic-coding-quickstart#529.Smaller borrowable detail
Docker's
lifecycle@1files[]entries carry nouser:field at all, with the rationale: "A path only root can own is an install hook's job — whatinstall[].useris for." That is a cleaner statement of the rule #436 violated, and it is enforced by the grammar's shape rather than by review.AI-assisted (OpenCode), on operator instruction. Research summary — treat the quoted spec text as the authority, not my paraphrase.