Skip to content

fix(sdk): promote validated forwarder identity on Internal-scope requests#138

Merged
I-am-nothing merged 2 commits into
mainfrom
fix/internal-scope-ctx-identity
Jul 4, 2026
Merged

fix(sdk): promote validated forwarder identity on Internal-scope requests#138
I-am-nothing merged 2 commits into
mainfrom
fix/internal-scope-ctx-identity

Conversation

@I-am-nothing

Copy link
Copy Markdown
Contributor

Problem

Internal scope never promotes forwarded identity into ctx on the dev plane — tunnel-served Internal handlers reading ms.AppID(ctx) get 401. Latent in ms-app-modules#30; blocks the #31 cross-read E2E.

Fix

Promotion lands in the Internal-scope auth middleware, at the same altitude as requireProxy: identity is derived once at the wire boundary and installed into ctx before any handler runs, so handlers stay transport-agnostic (ms.AppID(ctx), never headers). Trust model is identical to requireProxy — forwarded identity headers are trusted only after the validated internal secret authenticates the platform wire; the deployed envelope identity is never clobbered (envelope-derived ctx identity always wins when present).

Verification

gofmt -l clean on all 3 touched files; go build ./... OK; go vet ./auth/... ./internal/core/... OK; go test ./... all packages ok; targeted go test -count=1 -run "InternalAuth|RequireProxy|TunnelForwarded" ./auth/... ./internal/core/... verbose: all PASS including the 6 new auth tests and the end-to-end internal/core test. Negative verification reproduced independently (middleware reverted → both regression tests fail with identity nil / ms.AppID=""), worktree restored to clean at commit bd614c3.

Relationship

Independent of open #137; trivial rebase whichever merges first.

🤖 Generated with Claude Code

I-am-nothing and others added 2 commits July 5, 2026 04:40
…ests

On the dev/tunnel plane, dispatch strip-then-injects the X-MS-User-ID /
X-MS-App-ID / X-MS-App-Role identity headers on every Internal-scope
forward, but internalAuth only validated the platform secret and never
promoted those headers into ctx. Any Internal handler reading
ms.AppID(r.Context()) therefore got "" when tunnel-served and 401'd:
ms-app-modules#30 fixed oauth-google's platformCallback to read ctx
identity (correct for the deployed path) leaving it latently broken
over the tunnel, and the new cross-read route (ms-app-modules#31) hit
the same wall, blocking the dev-to-dev and dev-to-deployed E2Es.

Fix at the shared seam: after internalAuth's constant-time secret check
succeeds, promote the forwarder-injected identity triple into ctx via
promoteForwarderIdentity — the exact mechanism extracted from
requireProxy's success path, so both HTTP header-ingestion paths share
one helper. The validated secret is the same trust signal requireProxy
relies on: identity headers on this path can only come from the
platform wire (dispatch / dev tunnel proxy).

Fail-closed properties preserved:
- promotion fires ONLY on the secret-validated success path — never on
  the local no-secret bypass (unauthenticated transport), never on the
  401/503 rejection branches
- an identity already on ctx wins (deployed path: PayloadTrusted
  early-returns before promotion, so the envelope identity set by
  runtime.InjectResources stays authoritative)
- empty headers never mint an identity (same rule InjectResources
  applies to an all-empty envelope)

Covers every internal surface through the single cached m.internalAuth
closure: Module.Internal() routes plus the system event/cron/task
mounts.

Note: PR #137 (feat/ctx-identity-accessors) touches auth/middleware.go
doc comments and adds ms.UserID/ms.AppRole accessors; this branch is
cut from origin/main with edits kept minimal so a rebase across #137
(in either landing order) stays trivial.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-op branches

Simplify-pass follow-up on the Internal-scope identity promotion.

- Efficiency: the helper returned context.Context, forcing both call sites
  into an unconditional r.WithContext() — an http.Request clone (~500B) even
  on the two no-op branches (identity already preset, or all-empty headers).
  For internalAuth that was a new regression: its pre-fix success path was a
  zero-alloc next.ServeHTTP(w, r). Return *http.Request instead and hand back
  r unchanged on the no-op branches, so a headerless system-to-system internal
  call (events, crons) stays allocation-free on its hot path.
- Doc: the helper comment claimed it was shared by "the two secret-validated
  HTTP paths", reading as if only two exist. platformAuth step 4 is a third,
  deliberately stricter (all-three-required) hand-rolled variant — name it as
  the intentional exception so the doc no longer overclaims.

gofmt/build/vet clean; go test ./auth/... ./internal/core/... green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@I-am-nothing I-am-nothing merged commit a507259 into main Jul 4, 2026
1 check passed
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