Skip to content

feat(providers): support proxy-delivered static credentials - #2

Open
johnnygreco wants to merge 4 commits into
mainfrom
johnny/proxy-delivered-provider-auth
Open

feat(providers): support proxy-delivered static credentials#2
johnnygreco wants to merge 4 commits into
mainfrom
johnny/proxy-delivered-provider-auth

Conversation

@johnnygreco

@johnnygreco johnnygreco commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Summary

Add an opt-in delivery: proxy mode for static provider credentials. It keeps both the real credential and OpenShell's endpoint-bound placeholder out of the sandbox environment, then sets the complete authentication header at the last outbound proxy step, after network policy, L7 rules, and middleware have admitted the request.

What this does and does not change:

  • Where the credential lives. The workload never holds the secret or a placeholder. Nothing credential-shaped can appear in tool output, conversation context, or persisted session history when an agent inspects its environment.
  • When authentication happens. Policy and middleware inspect the request before OpenShell authenticates it. This is what the Pi context-admission integration depends on.
  • Not who can authenticate. Proxy delivery is ambient authority for the bound endpoint. Any sandbox process that reaches the endpoint through the inspected proxy sends an authenticated request, exactly as it could today with an environment placeholder, which is also available to every process in the sandbox. This PR does not narrow that set; binary-scoped injection remains a roadmap item for both modes.
flowchart LR
    subgraph Sandbox
        A[Agent or tool] -->|Authorization: public value| B[Inspected HTTP request]
        C[Environment exploration] --> D[Conversation and session history]
        E[No credential variable<br/>No OpenShell placeholder] -.-> C
    end

    B --> F{Network, L7, and<br/>middleware policy allow?}
    F -->|No| G[Reject request]
    F -->|Yes| H[Resolve binding through<br/>request-scoped resolver]
    H --> I[Replace complete auth header<br/>Emit OCSF event]
    I --> J[Provider API]
Loading
Delivery mode Workload environment Final authentication header
environment (default) Contains an endpoint-bound OpenShell placeholder Placeholder is resolved for an authorized destination
proxy Contains neither the credential nor its OpenShell placeholder Proxy inserts the real credential after request admission

Related Issue

No accepted issue is available: this is a fork-only integration PR, and Issues are disabled on johnnygreco/OpenShell.

The motivating integration is OpenShell Research PR #38 on the OpenShell Research branch johnny/pi-attested-admission. That work combines Pi context admission with post-policy credential delivery so prompt inspection happens before OpenShell authenticates the provider request.

Design

The static credential binding is the sandbox's single source of truth. The gateway copies the delivery mode, auth style, and header name onto the StaticCredentialBinding it already sends for every static credential. The proxy asks the live provider credential state for proxy-delivered bindings that authorize the request's host, port, and path, resolves each through the same request-scoped resolver used for placeholders, and replaces the header. It never consults profile metadata at request time, and proxy-delivered credentials are no longer carried in the dynamic (token grant) credential map, so that map keeps its original meaning.

Ambiguity is rejected at attach time, not resolved at request time. Two attached providers cannot bind proxy-delivered credentials to overlapping selectors on the same port, even at different specificity. Token grants allow the more specific selector to win because the request carries no ambiguity the proxy would need to resolve; with proxy delivery the request carries no credential at all, so there is nothing to disambiguate with. The proxy still fails closed if it ever sees two distinct matching credentials. Aliases of one credential (multiple env_vars) collapse into a single header.

Values are validated when the provider is created or updated. A bearer value must be token68; a named header value must not contain control characters. Previously a stored key with a space would pass creation and fail every request with a misleading "token grant returned a malformed access token" 502. The proxy still validates immediately before injection as a backstop, with a proxy-delivery-specific message.

Every injection is auditable. Success and failure emit OCSF HTTP activity events naming the environment key, header, and endpoint, never the value, mirroring the token grant events. Failure also returns 502 provider_authentication_failed and, on the forward-proxy path, ends any open middleware session.

Changes

  • Add ProviderCredentialDelivery to the provider profile credential and static credential binding protocol; add auth_style and header_name to the binding. Environment delivery remains the default and leaves the new fields empty.
  • Parse, validate, and export delivery: proxy in custom provider profiles. Validation requires one proxy-delivered credential per profile, at least one env_vars entry, inspected protocol: rest endpoints without tls: skip, bearer or header placement with a valid non-framing header name, and no token_grant in the same profile.
  • Omit proxy-delivered credentials from the child workload environment while retaining supervisor-side endpoint resolution.
  • Drive injection from the binding in the REST relay, the passthrough relay, and the plain forward proxy, after middleware evaluation. Fail closed on a missing resolver, an unavailable value, a poisoned credential registry, or competing providers.
  • Validate proxy-delivered credential values on provider create and update, and share the token68 check between the gateway and the proxy.
  • Emit OCSF success and failure events for proxy delivery; treat a poisoned token grant registry as an error instead of "no credential".
  • Generalize the gateway's token grant ambiguity check to runtime-injected credentials and finish the rename.
  • Document the profile syntax, value constraints, ambiguity rule, failure modes, and public CLI troubleshooting; update the sandbox and gateway architecture docs.
  • Add an e2e test that creates a proxy-delivered provider, rejects a non-token68 value at create time, and verifies from inside a sandbox that the variable is absent and the upstream receives the real credential.
  • Regenerate the Go protobuf bindings.

Testing

  • mise run pre-commit passes
  • Unit tests added/updated
  • E2E tests added/updated

Unit and integration coverage added in this PR:

  • openshell-core: proxy-delivered bindings are listed only for matching endpoints; value validation per auth style.
  • openshell-providers: delivery round-trips; rejections for missing env vars, multiple proxy credentials, token grant conflicts, non-REST endpoints, tls: skip, unsupported auth styles, framing and invalid header names, and unknown delivery values.
  • openshell-server: resolved bindings carry placement metadata and stay out of the dynamic credential map; environment delivery leaves the fields empty; create and update reject non-token68 bearer values without echoing them; overlapping proxy bindings at different specificity are ambiguous.
  • openshell-supervisor-network: header replacement without body change; named header added when absent; passthrough when no binding matches; alias collapse and competing-provider rejection; fail-closed without a resolver and after revocation; bearer value validation. Request-level tests drive relay_rest (allowed path injects, denied path never reaches upstream), relay_passthrough_with_credentials, and a real handle_forward_proxy round trip against a local upstream.

Verification:

  • cargo test -p openshell-core -p openshell-providers -p openshell-server -p openshell-supervisor-network
    • 445 core tests passed
    • 123 provider tests passed
    • 1,459 server tests passed, plus 28 server integration tests
    • 1,277 supervisor-network tests passed, plus 8 supervisor-network integration tests
    • the new handle_forward_proxy round trip ran (not skipped) on this Linux host
  • mise run pre-commit
  • mise run go:proto:check
  • OPENSHELL_E2E_DOCKER_TEST=provider_proxy_delivery e2e/rust/e2e-docker.sh against an ephemeral Docker gateway with a supervisor image built from this tree
    • standalone CLI conformance passed
    • proxy_delivered_credential_stays_out_of_sandbox_and_is_injected_upstream passed: create rejects a non-token68 bearer value without echoing it, the sandbox sees TOKEN_ABSENT, and the host upstream receives Authorization: Bearer <real secret> in place of the public value

Checklist

  • Follows Conventional Commits
  • Commits are signed off (DCO)
  • Architecture docs updated (architecture/sandbox.md, architecture/gateway.md)

Signed-off-by: Johnny Greco <jogreco@nvidia.com>
Signed-off-by: Johnny Greco <jogreco@nvidia.com>
Make the static credential binding the sandbox's single source of truth
for proxy-delivered credentials. The gateway copies the delivery mode,
auth style, and header name onto the binding, and the proxy resolves
matching proxy-delivered bindings through the request-scoped resolver
instead of consulting the dynamic token grant map, which returns to
carrying token grants only.

Validate proxy-delivered credential values against their declared
placement when a provider is created or updated, so a non-token68
bearer value fails with a message that names the credential rather
than as a 502 on every request. Emit OCSF HTTP activity events for
every successful and failed injection, treat a poisoned credential
registry as an error instead of "no credential", and end any open
middleware session when forward-proxy injection fails.

Add request-level tests through relay_rest, the passthrough relay, and
a real handle_forward_proxy round trip, profile validation tests for
every proxy-delivery rejection, gateway tests for create/update value
validation and binding metadata, and a Docker e2e test that verifies
the variable is absent from the sandbox and the upstream receives the
injected credential. Document the ambiguity rule, value constraints,
and failure modes, and update the sandbox and gateway architecture
docs.

Signed-off-by: Johnny Greco <jogreco@nvidia.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.

1 participant