Skip to content

feat(worker): project secret bundles on the openshell venues, with per-host placeholders (#704) - #720

Merged
edgehero merged 1 commit into
mainfrom
feat/openshell-bundle-endpoints-704c
Oct 20, 2026
Merged

edgehero merged 1 commit into
mainfrom
feat/openshell-bundle-endpoints-704c

Conversation

@edgehero

Copy link
Copy Markdown
Owner

Part 3 of 4 for #704 (parts 1 and 2 are #718 and #719, both merged). Both openshell venues now carry project secret bundles. This replaces part 2's openshell-bundle-unsupported refusal.

Declaring an endpoint

"secretBundles": { "shop": { "profile": "shop-env", "ref": "shop", "project": "shop",
  "endpoints": { "STRIPE_KEY": { "host": "api.stripe.com", "auth": "bearer" } } } }
  • What the agent sees: STRIPE_KEY is a placeholder in its env. The real value is used only at api.stripe.com, through a per-job provider (<sandbox>-b<n>) bound to that host's policy endpoints only.
  • Why one provider per host: at 0.1.4, credential_binding names a single provider per endpoint, and a provider binds every key it holds.
  • Overrides: a run.secrets value that overrides a declared name goes into that host's provider.
  • auth documents intent. The gateway checks it when the profile is imported, but no supervisor code reads it for a static credential: the swap happens wherever the placeholder appears.

Plain variables stay out of the exec command and the gateway log

At 0.1.4 the gateway logs the first 120 characters of every exec command at INFO (sandbox.rs, command_preview). So plain bundle variables never travel as exec --env:

  • The worker writes them to a 0600 bundle-env.json in the job directory.
  • The runner applies the file before pi's runtime exists. JSON keeps newlines, so a PEM works.
  • This needs the new image capability bundleEnvFile. An older image is refused before spend (openshell-bundle-image-too-old).
  • A job image release is owed.

Refused before the sandbox and before spend

Each refusal names the host or the variable, never a value:

  • openshell-bundle-endpoint-not-allowed: the host is not exactly an endpoint of the generated policy. A reviewed file may not widen egress.
  • openshell-bundle-endpoint-shared: a forge, model, model-endpoint or HTTP MCP host. Binding it would make that host's own credentials fail with a 403.
  • openshell-bundle-endpoint-unknown: an endpoints name the bundle did not deliver.
  • openshell-bundle-name-taken: a name the sandbox sets itself, or a PI_ name.
  • openshell-bundle-unpassable: a value or a key count over the gateway's limits.

Lifecycle

  • Read-back accepts exactly one _provider_<name> layer per attached provider.
  • Teardown deletes the sandbox first, then the providers.
  • The reaper sweeps the bundle providers and any leftover profiles.
  • Profile files are deleted as soon as their import answers.
  • The record gains placeholders and plain counts.

Specs

  • Amended: DES-OPENSHELL-VENUE, DES-PROJECT-SECRET-BUNDLES, INT-RUN-HISTORY-FILE-CONTRACT, INT-TRIGGERS-FILE-CONTRACT, INT-CONTAINER-JOB-INPUTS, REQ-TRIGGER-SECRETS and OQ-047.
  • OQ-028 is amended and stays closed on the openshell venues.
  • New OQ-048: a manager reached through OpenShell itself, either an operator-owned provider or the gateway's credential-driver socket. It is not built. The resolver seam already covers a read-only service account, and nothing manager-specific is in the repository.
  • UNCHANGED, checked: CONST-VENUE-EGRESS-LAYER, CONST-BUDGET-BEFORE-TOKENS, CONST-RETRY-INFRA-ONLY.

Residuals

These cases are still refused only at run time, by the supervisor's 403. All are named in the docs:

  • On a trigger with no allowed-model list, an agent that switches to an overlay model whose baseUrl is a bundle host. run.models or PI_ALLOWED_MODELS closes this.
  • A stdio MCP server's own HTTP calls that carry a secret to a bundle host.

Verification

  • Review: two adversarial rounds, with every finding fixed.

  • Full suite with Valkey: 9523 pass, and the 2 failures are known (the macOS mcp test, and a service.test.mjs timing test that passes 79/79 alone).

  • Checks: revision-row, test-count, temp-dir and dated-fixture all pass.

  • The verify-image.sh bundleEnvFile case passed on a locally built image with this runner.

  • The conformance lanes (copy and bind) gain a bundle case:

    • a placeholder in the env and in /proc/self/environ;
    • the real value at the stand-in, and a 403 elsewhere;
    • plain values applied;
    • no value in the gateway log;
    • nothing left after a killed worker's sweep.

    Only CI can prove these.

@edgehero
edgehero force-pushed the feat/openshell-bundle-endpoints-704c branch from 0433cb9 to 542f7c8 Compare October 20, 2026 13:17
…r-host placeholders (#704)

Replaces part 2's openshell-bundle-unsupported refusal with real support on
both openshell venues (copy and bind).

- A bundle variable named in the bundle's endpoints map becomes a key of a
  per-job provider for that host (<sandbox>-b<n>), bound only to that
  host's policy endpoints. At the pin, credential_binding names one
  provider per endpoint, and a provider binds all of its keys. The agent
  sees a placeholder, and the real value is used only at that host. A
  run.secrets value that shadows a declared endpoint name goes into that
  host's provider.
- The other bundle variables are plain, as on the other venues. They no
  longer travel in the exec command: at 0.1.4 the gateway logs the first
  120 characters of every exec command at INFO (sandbox.rs,
  command_preview). The worker writes them to a 0600 bundle-env.json in
  the job directory, and the runner applies it before pi's runtime
  exists. That needs the new image capability bundleEnvFile; an older
  image refuses before spend (openshell-bundle-image-too-old).
- The following are refused before the sandbox and before spend. The
  refusal names the host or variable, never a value:
  - a host that is not exactly a policy endpoint (a reviewed file may not
    widen egress);
  - a forge, model, model-endpoint or HTTP MCP host, whose own
    credentials would then 403;
  - an endpoint name the bundle did not deliver;
  - a name the sandbox sets itself, or a PI_ name;
  - a value or a count the gateway would refuse.
- Read-back accepts exactly one _provider_<name> layer per attached
  provider. Teardown deletes the sandbox, then the providers. The reaper
  sweeps the bundle providers and leftover profiles. Profile files are
  deleted as soon as their import answers.
- The record gains placeholders and plain counts.
- The conformance lanes (copy and bind) gain a bundle case: a placeholder
  in env and /proc/self/environ, the real value at the stand-in, a 403
  elsewhere, plain values applied, no value in the gateway log, and
  nothing left after a killed worker's sweep.

Specs: DES-OPENSHELL-VENUE, DES-PROJECT-SECRET-BUNDLES,
INT-RUN-HISTORY-FILE-CONTRACT, INT-TRIGGERS-FILE-CONTRACT,
INT-CONTAINER-JOB-INPUTS, REQ-TRIGGER-SECRETS and OQ-047 are amended.
OQ-028 is amended and stays closed on the openshell venues. OQ-048 is new:
a manager through OpenShell itself (an operator-owned provider, or the
gateway's credential driver socket). It is not built. The resolver seam
already covers a read-only service account, and nothing manager-specific
is in the repository. UNCHANGED, checked: CONST-VENUE-EGRESS-LAYER,
CONST-BUDGET-BEFORE-TOKENS and CONST-RETRY-INFRA-ONLY. A job image release
is owed for bundleEnvFile.

Signed-off-by: Rob Boerman <robboerman@live.nl>
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.

1 participant