Skip to content

feat(dev): lambda-invoke shim — HTTP stand-in for the prod Lambda transport#129

Merged
I-am-nothing merged 1 commit into
mainfrom
feat/lambda-dev-shim
Jul 3, 2026
Merged

feat(dev): lambda-invoke shim — HTTP stand-in for the prod Lambda transport#129
I-am-nothing merged 1 commit into
mainfrom
feat/lambda-dev-shim

Conversation

@I-am-nothing

@I-am-nothing I-am-nothing commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

What

Adds POST /__mirrorstack/lambda-invoke (dev/HTTP mode only, never mounted in real Lambda mode): dispatch's localHTTPInvoker (MS_MODULE_LAMBDA_DEV_URL) POSTs the LambdaRequest envelope here, and the shim feeds it through the same runtime.NewLambdaHandler(m.router) closure production uses — so a locally "deployed" module is served through the real envelope path with zero module-code changes. This is the dev shim api-platform lambda_transport.go already anticipated.

Trust gate (the important part)

NewLambdaHandler injects the envelope's typed userId/appId/appRole/appSchema into ctx as trusted identity, and PlatformAuth honors preset identity with no secret check — so an ungated shim would let any local caller forge admin identity. The shim gates itself BEFORE invoking the handler, mirroring internalAuth's matrix:

  • MS_LAMBDA_INTERNAL_SECRET set → constant-time compare against the envelope's X-MS-Internal-Secret (dispatch injects it on every invoke); 401 on mismatch
  • unset while the platform-token chain IS configured (tunnel/dev-runner) → 503 fail-closed
  • nothing configured → bypass (plain mirrorstack dev, where this surface is already open by prior design)

Dedicated env var because the dev runner points MS_PLATFORM_TOKEN[_FILE]/MS_INTERNAL_SECRET at the per-session tunnel token / log-shipper secret — neither is what dispatch puts in envelopes. Self-targeting envelopes are rejected outright; rejections use outer HTTP statuses (dispatch treats ≥300 as a generic transport fault, so nothing leaks).

Also adds auth.WithPayloadTrust (context-only, set solely by NewLambdaHandler): RequireProxy gives payload-trusted requests the same pass-through as Lambda mode — without it every Public/Platform route 403s not_proxied through the envelope path whenever MS_PLATFORM_TOKEN_FILE is set.

Verification

  • Unit: wire-contract round-trip (exact JSON field names both directions), gate matrix (401/503/bypass), payload-trust vs not_proxied, x-ms-* identity-header stripping preserved.
  • Live E2E on the dev stack (with mirrorstack-ai/api-platform#284 writer + module_deploys row for oauth-core): dispatch /v1/apps/twkpa-edu/oauth-core/public/me → shim (POST /__mirrorstack/lambda-invoke 200 + envelope identity in module logs) → correct response; flipping the deploy row to disabled falls back to the WSS tunnel; oauth-google (no deploy row) stayed on the tunnel throughout.

🤖 Generated with Claude Code

Mount POST /__mirrorstack/lambda-invoke on the module's dev HTTP server
(never in Lambda mode): dispatch's localHTTPInvoker POSTs LambdaRequest
envelopes there via MS_MODULE_LAMBDA_DEV_URL, and the shim feeds them
through the same runtime.NewLambdaHandler closure production uses.

Because NewLambdaHandler injects the envelope's typed identity into ctx
before any router middleware runs — and on the dev HTTP path PlatformAuth
passes preset identity with no secret check — the shim gates itself on
the envelope's X-MS-Internal-Secret against a dedicated module-side
MS_LAMBDA_INTERNAL_SECRET (the MS_PLATFORM_TOKEN chain carries the
per-session tunnel token, which lambda envelopes never hold): enforce
with constant-time compare when set, 503 fail-closed when a platform
secret is configured without it, bypass only when nothing is configured
(plain `mirrorstack dev`, matching every other guard). Gate rejections
use outer 4xx/5xx (dispatch treats >=300 as a transport fault); a
delivered invoke is always outer 200 with the module's status inside
the envelope. Self-targeting envelopes are rejected outright.

NewLambdaHandler now marks its synthetic request payload-trusted
(auth.WithPayloadTrust, context-only — never settable from inbound
data), and RequireProxy honors the mark with the Lambda-mode
pass-through, so Public/Platform routes stop 403ing not_proxied through
the shim under the standard MS_PLATFORM_TOKEN_FILE dev-runner config.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019grh7whQS1xhPfQpaZ4bNG
@I-am-nothing I-am-nothing merged commit 96191cd into main Jul 3, 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