Skip to content

feat(service): add opt-in bearer authorization passthrough #3851

Description

@derekwaynecarr

User Story

As a user exposing an HTTP or WebSocket application from a sandbox, I want to
opt that individual service into receiving the caller's bearer credential so
that the application can enforce its own authentication without weakening the
default credential boundary for other services.

Problem Statement

OpenShell service routing currently removes every incoming Authorization
header before proxying a request to a sandbox's loopback service. That behavior
is a safe default for gateway and edge credentials, but it also prevents an
application from receiving a bearer capability intended for the application
itself. Applications that authenticate an HTTP request or the initial WebSocket
handshake therefore cannot use the standard Authorization: Bearer ... flow
through an OpenShell service URL.

Impact / Why This Matters

This blocks authenticated application protocols such as an app server that
checks a capability during its WebSocket handshake. The practical workarounds
are to disable application authentication, move a credential into a query
parameter or cookie, use a nonstandard header, or bypass the exposed-service
route. Those alternatives either weaken the application's security posture,
increase the chance of credential disclosure, require application-specific
changes, or give up OpenShell's managed service URL and relay path.

The feature must remain opt-in. Automatically forwarding Authorization would
silently change the security boundary for every existing exposed service and
could deliver a gateway, edge, or caller credential to an untrusted sandbox
process.

Proposed Design

Add a persisted, per-service authorization mode with two effective behaviors:
strip and bearer_passthrough. Omitted, unspecified, legacy, and unknown
persisted values resolve to strip; unknown values in new API requests are
rejected. A service explicitly configured for bearer_passthrough may receive
zero or one syntactically valid Bearer authorization value, forwarded unchanged
to the loopback application. Duplicate, Basic, empty, and malformed values fail
with 400 Bad Request before the gateway contacts the application.

Surface the mode through standalone service exposure, create-time service
exposure, endpoint get/list responses, CLI commands and structured output, and
the curated Rust, Python, Go, and TypeScript SDKs. Continue stripping proxy
authorization, edge identity headers, forwarded client certificates, and edge
authentication cookies in every mode. The application—not OpenShell—validates
the bearer credential.

Acceptance Criteria

  • Existing and newly exposed services strip Authorization unless the matched
    persisted endpoint explicitly selects bearer passthrough.
  • Omitted or UNSPECIFIED API values materialize or resolve to STRIP, and a
    legacy stored endpoint without the new field retains stripping behavior.
  • Unknown authorization-mode values are rejected with INVALID_ARGUMENT
    before endpoint or sandbox persistence.
  • An opted-in HTTP service receives exactly one syntactically valid Bearer
    authorization value unchanged.
  • The same rule applies to the initial WebSocket upgrade request; subsequent
    WebSocket frames require no authorization-header handling.
  • An opted-in service may receive a request without Authorization so that the
    application can return its own authentication response.
  • Duplicate authorization fields and empty, Basic, or malformed credentials
    receive 400 Bad Request without contacting the sandbox service.
  • Gateway and edge identity headers, proxy authorization, and edge access
    cookies remain stripped in both modes.
  • Re-exposing an endpoint can update its target port and authorization mode
    using the existing compare-and-swap behavior.
  • Expose, get, list, and create-time responses report the effective mode;
    structured CLI output includes authorization_mode.
  • The setting round-trips through the public protobuf API and curated Rust,
    Python, Go, and TypeScript SDKs.
  • Raw authorization values never appear in errors, tracing fields, OCSF
    events, or test output.
  • Documentation explains the secure default, opt-in behavior, credential trust
    boundary, HTTP/WebSocket semantics, and interaction with the gateway's
    existing listener and TLS configuration.

Alternatives Considered

  1. Continue unconditional stripping. This best protects gateway identity
    material but leaves standards-based authenticated sandbox applications
    unusable through service URLs.
  2. Forward Authorization for every service. This is simple but is an
    unsafe compatibility break because existing sandbox applications would
    begin receiving credentials without consent.
  3. Use a custom application header. This avoids overloading
    Authorization, but requires application- and client-specific adaptations
    and does not support servers that require standard Bearer authentication
    during a WebSocket handshake.
  4. Translate into a new internal header. Translation changes the
    application protocol and creates another security-sensitive convention;
    it also does not work with unmodified application servers.
  5. Add a dedicated service listener, domain, certificate, or gateway-managed
    application credential.
    These may provide stronger long-term separation
    but materially expand deployment and PKI scope. They are not required for a
    per-endpoint opt-in and should be evaluated separately.

Agent Investigation

No response

Checklist

  • I've reviewed existing issues and the architecture docs
  • This is a design proposal, not a "please build this" request

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Featurestate:acceptedA maintainer decided OpenShell should pursue this issue

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions