Skip to content

feat(api,mcp): migrate 7 stubs; security audit fixes; lock stdio-only MCP#621

Open
lakhansamani wants to merge 1 commit into
feat/multi-protocol-apifrom
feat/stubs-and-mcp-hardening
Open

feat(api,mcp): migrate 7 stubs; security audit fixes; lock stdio-only MCP#621
lakhansamani wants to merge 1 commit into
feat/multi-protocol-apifrom
feat/stubs-and-mcp-hardening

Conversation

@lakhansamani
Copy link
Copy Markdown
Contributor

Stacked on top of #620. Address the MCP security audit, lock down stdio-only, and implement 7 of the 17 stub gRPC methods.

Summary

Two concerns in one PR:

  1. Security audit findings (CRITICAL + HIGH) fixed — `Session` no longer MCP-exposed (C1), `--mcp-bearer` propagates identity into gRPC (H1), recovery interceptor stops logging panic values that could carry credentials (H2). Stdio-only enforced at code level with a reflection-based test that fails the build if anyone adds RunHTTP/ListenTCP/etc.
  2. 7 stubs migrated to real handlers: `Profile`, `Permissions`, `Logout`, `Revoke`, `ValidateJwtToken`, `ValidateSession`, `Session`. Each follows the established `SignUp` pattern — `internal/service/{op}.go` for the transport-agnostic logic, GraphQL resolver delegates, gRPC handler projects to proto types.

Security audit fixes

ID Finding Fix
C1 `SessionResponse` contains access/refresh/id tokens + authenticator secret Flipped `mcp_tool.exposed = false` on Session. Stays available via gRPC + REST + GraphQL for legitimate consumers.
H1 No MCP→gRPC auth propagation New `--mcp-bearer` flag. MCP server stamps `Authorization: Bearer ` on every outgoing gRPC call.
H2 Recovery interceptor logs panic values (could leak credentials) `.Interface("panic", r)` → `.Str("panic_type", fmt.Sprintf("%T", r))`. Regression test `TestRecovery_DoesNotLogCredentialBearingPanicValue`.

Stdio-only MCP transport (code-level enforcement)

  • `internal/mcp/server.go` — type-level comment documents the design constraint.
  • `internal/mcp/transport_test.go` — `TestServer_StdioOnly` reflects over `*Server`'s exported methods; fails the build if any method's name contains `http` / `tcp` / `sse` / `websocket` / `listen` / `serve` / `run` (other than the allow-listed `RunStdio`).
  • `cmd/mcp.go` docstring + cobra Long help say "stdio is the only supported transport".

7 stub migrations

Method Real now? Notes
Profile Returns User; auth via existing TokenProvider helper
Permissions Calls `authorization.GetPrincipalPermissions`
Logout Drops memory-store session + emits expired Set-Cookie
Revoke RFC 7009 typed mirror
ValidateJwtToken Claims projected as proto Struct
ValidateSession Cookie-from-meta resolution; supports explicit cookie param
Session Rotates session token + sets new cookie; not MCP-exposed (C1)

Still stubbed (10 ops, follow-up PRs each): `Login`, `MagicLinkLogin`, `VerifyEmail`, `ResendVerifyEmail`, `VerifyOtp`, `ResendOtp`, `ForgotPassword`, `ResetPassword`, `UpdateProfile`, `DeactivateAccount`. Each is a substantial state machine — focused individual PRs.

Test plan

  • `go build ./...` clean
  • `buf lint proto` + `buf format -d proto` clean
  • Full SQLite integration suite (67s) green — no behaviour drift on the 7 migrated ops vs prior GraphQL implementations
  • `TestMCPListAndCallMeta` updated: 3 MCP tools (meta/profile/permissions); `session` deliberately absent (C1)
  • `TestMCPToolErrorSurfacesAsIsErrorResult` exercises anonymous identity-bearing call (H1 + tool error shape)
  • `TestServer_StdioOnly` passes
  • `TestRecovery_DoesNotLogCredentialBearingPanicValue` passes
  • `TestAuthorizerServiceStubsReturnUnimplemented` shrunk by 7 entries
  • CI green on this stacked PR

🤖 Generated with Claude Code

… MCP

Implements 7 of the 17 stubbed AuthorizerService methods (Profile,
Permissions, Logout, Revoke, ValidateJwtToken, ValidateSession, Session)
following the established service-layer pattern, and addresses the
security audit findings against the MCP surface.

SECURITY AUDIT FIXES

C1 — Session response carries access_token / refresh_token / id_token /
authenticator_secret / recovery_codes. The proto annotation on Session
flipped to mcp_tool.exposed = false so those credentials never land in
an LLM transcript. Session remains available via gRPC + REST + GraphQL
for legitimate browser/server-to-server consumers.

H1 — MCP→gRPC auth propagation. New `--mcp-bearer` flag on the
`authorizer mcp` subcommand; the MCP server stamps `Authorization:
Bearer <token>` on every outgoing gRPC call. Identity-bearing tools
(profile, permissions) now have a caller to attribute to; anonymous
runs still work for the public Meta tool but identity-bearing tools
surface a clean unauthorized error.

H2 — Recovery interceptor redacts panic values. The recovered value is
no longer dumped via `.Interface("panic", r)` (which would have logged
credentials if a handler ever panicked with the request struct); only
the panic type is logged for triage. Regression test included.

STDIO-ONLY MCP TRANSPORT

internal/mcp/server.go — explicit type-level documentation: stdio is
the ONLY supported transport. The Server has no RunHTTP / RunTCP /
RunSSE methods, intentionally.

internal/mcp/transport_test.go — `TestServer_StdioOnly` reflects over
*Server's exported methods and fails the build if anyone adds a method
whose name suggests a network transport (RunHTTP, ListenTCP, ServeWS,
etc.). To add a transport: implement an MCP-side auth interceptor
first, then update the allow-list.

cmd/mcp.go — docstring + CLI long help explicitly state "stdio only".

7 STUB MIGRATIONS

internal/service: profile.go, permissions.go, logout.go, revoke.go,
validate_jwt_token.go, validate_session.go, session.go,
permission_check.go (shared helper). All follow the SignUp pattern:
take RequestMetadata, return (result, *ResponseSideEffects, error).

internal/grpcsrv/handlers: authorizer.go grows 4 real method
implementations (Profile, Permissions, Logout, Revoke,
ValidateJwtToken, ValidateSession, Session). project.go adds
projectUser / projectAuthResponse / projectAppData / claimsToAppData /
protoToModelPermissions helpers shared across methods.

internal/graphql: resolvers for the seven ops become thin delegations
(same pattern as Signup + Meta).

internal/cookie: BuildDeleteSessionCookies added; DeleteSession now
delegates to it (transport-agnostic mirror of the existing pattern).

internal/service/provider.go: Dependencies grows AuthorizationProvider;
the four new methods land on the Provider interface. All call sites
(cmd/root, cmd/mcp, test_helper) wire it through.

TESTS

- TestRecovery_DoesNotLogCredentialBearingPanicValue (H2 regression)
- TestServer_StdioOnly (transport lock-down)
- TestMCPListAndCallMeta now expects 3 MCP tools (meta/profile/permissions);
  session was DROPPED per C1.
- TestMCPToolErrorSurfacesAsIsErrorResult exercises anonymous call to
  identity-bearing tool (formerly the "stubbed tool" test).
- TestAuthorizerServiceStubsReturnUnimplemented shrunk by 7 entries.
- Full SQLite integration suite (67s) still green — no regression on
  the existing GraphQL behaviour for any of the 7 migrated ops.

STILL STUBBED (10 ops, follow-up PRs)

Login, MagicLinkLogin, VerifyEmail, ResendVerifyEmail, VerifyOtp,
ResendOtp, ForgotPassword, ResetPassword, UpdateProfile,
DeactivateAccount. Each is a substantial state machine; better as
focused individual PRs than rushed in a batch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.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