Repository navigation
Conversation
| if (!grant) { | ||
| throw Object.assign(new Error('Gateway MCP requires a control-plane grant'), { | ||
| code: 'GATEWAY_GRANT_MISSING', | ||
| }); |
There was a problem hiding this comment.
[P1] Preserve existing executions before enabling the mandatory-grant runtime path
@eipasteur An execution started with Gateway disabled can park at a human gate, lose its warm runtime session, and then resume after enable_mcp_gateway is enabled. Its published durable orchestrator still has the old configuration and no grant issuer, while the standard runtime can restart on the new configuration: standard revisions have runtimeEndpoint: null, and resolveRuntimeTarget() does not pin the invocation using runtimeVersion. The resumed request therefore reaches this check without a grant and fails with GATEWAY_GRANT_MISSING.
This also affects executions started after deploying the new code with flags off. In #614's engine-data mode, the request fails even earlier: resolveInvocationAgentAuth() reads META through the remote store before calling the broker. My local reproduction using the real dispatcher/auth resolver/store returned HTTP 500 with No engine data grant for this invocation; broker and handler calls were both zero. Leaving enforce_runtime_grants=false does not protect that path.
Please either keep existing executions routed to a compatible pinned runtime revision/configuration, or require and enforce draining them before the first remote-path flag is enabled. The rollout guide in #616 currently drains them only after enabling Gateway and engine-data. Keep the missing-grant rejection itself; an unauthenticated fallback would weaken the boundary.
Regression/acceptance checks:
- Start an execution with flags off, park it, stop its runtime session, enable Gateway, and resume it.
- Repeat for engine-data transition mode with strict enforcement off.
- Verify compatible resumption, or prevent activation while such executions remain; include parked executions in the drain condition.
- Keep missing/forged grants rejected on the remote path.
The failure was reproduced locally at stack head 22ec812c; the live AWS migration scenario was not exercised.
There was a problem hiding this comment.
Agreed, thanks for the repro. I kept the missing-grant rejection and went with draining before activation, enforced rather than documented:
scripts/check-active-executions.mjsruns indeploy-terraform.shon every saved plan (and again at apply). It detects the runtime gaining the Gateway (this PR) or engine-data (feat(engine-data): route the runtime's engine data through grant-verified engine-data (opt-in) #614) configuration, so a retry after an apply that failed before the runtime was updated is still caught, and refuses the plan while any execution is CREATED, RUNNING or WAITING, listing them. Fresh installs and plans that switch nothing skip the table read.- Operator path: stop starting intents until the deployment has succeeded, let running executions park or finish (
/canceldoes not accept RUNNING), cancel the parked and queued ones, apply, then rewind each to its current stage (its first stage if it had not reached one). The rewind starts a new orchestrator run on the new configuration, so it relaunches with grants; artifacts and workspace are kept. AIDLC_ALLOW_ACTIVE_EXECUTIONS=1overrides, and still prints the executions that will fail.- The rollout guide in feat(terraform): remove the runtime role's direct data access under enforced runtime grants #616 now drains before each of the two flags, not after.
Tests cover activation per flag, retry after a partial apply, queued/running/parked executions vs drafts, fresh install, read failures and the override; the script was also run against a real process table. Not exercised: the live migration scenario on a deployed stack. A direct terraform apply skips the check (documented), and new starts during the apply are prevented by the procedure, not enforced. Pinning the runtime revision per execution would allow migrating without the drain, but it is a much larger change; happy to take it as a follow-up if we need hot migration.
| if (requireActive && !ACTIVE_EXECUTION_STATUSES.includes(execution.status)) { | ||
| throw new Error('MCP execution is not active'); | ||
| } |
There was a problem hiding this comment.
P2 — Reject grants after the execution’s run ID changes.
startup-context.js:149–151 checks the execution’s status, but compares orchestratorRunId only against the stage row. Recompose retires the execution’s run ID before resetting stage rows. During that interval, the execution can remain WAITING and the stage still carries the old run/callback IDs. The Gateway therefore accepts the retired grant. I reproduced this through the real grant signer, verifier, and Gateway handler: a signed old-run grant reached a stubbed createArtifact writer and was audited as allow/ok. DynamoDB write fencing does not protect that graph mutation, so stale work can modify artifacts after recompose has archived them.
Fix: compare the signed run ID with execution META during authorization, alongside the existing stage check. Add a regression test where META is retired but the stage still names the old run. This belongs in #613’s existing authorization logic; it does not require importing #615’s retirement machinery.
There was a problem hiding this comment.
Fixed in 3463512: authorization now also compares the signed run with META.orchestratorRunId. The regression test has META retired while the stage row still names the old run; it fails without the fix. The same check is in engine-data's stage grants (#614, 84e5efe), since graph writes have no store-side fence there either.
2bfafbb to
0bd1cc0
Compare
|
Rebased on |
0bd1cc0 to
89d4922
Compare
Complexity and test coverageAutomated quality report using code complexity and test coverage to highlight potential hot spots in this PR.
🟢 Improving · 🟠 Needs attention · 🔴 High risk · 🟣 Highest risk — = unavailable · n/a = not applicable · * = incomplete tests |
There was a problem hiding this comment.
@eipasteur Previous findings are solved, thank you.
Two focused questions on the Gateway integration after our meeting this morning:
- JWT validation: Is there a reason we use
AWS_IAMinbound plus a custom JWT verified in the target Lambda, rather than Gateway’s nativeCUSTOM_JWTauthorizer? I understand execution/stage checks would remain application-owned, and direct Lambda calls still need verification. Would native validation simplify the MCP path, or introduce more infrastructure than it removes? - Tool permissions: Did you consider AgentCore Policy for the tool allowlist? Would it replace meaningful application code, or mostly duplicate the existing shared rules? Since the runtime currently asserts the role, would trusted role claims be a prerequisite?
Please also rebase the stack.
…t-in) With enable_mcp_gateway (requires VPC networking) the runtime no longer runs the built-in aidlc MCP server in the agent's microVM. A stdio proxy registers the same tools and forwards each call, SigV4-signed, to an AWS_IAM AgentCore Gateway whose Lambda target (mcp-tools) runs the existing graph and process handlers. The orchestrator and discussions Lambdas sign a short ES256 grant with a KMS key only they may use. It names the space, intent, execution, stage lane and leg, mode and release-policy digest, travels in a propagated header and never reaches command handlers or the agent CLI. On every call the tools Lambda verifies it, rejects session context outside it, re-reads the execution and stage row (a finished, cancelled or superseded leg is denied), enforces the role's tool catalog and drops unknown arguments, and audits each allow, deny and handler error with its scope. Without a grant the runtime refuses to start the MCP instead of falling back. The tools Lambda gets scoped DynamoDB KMS access for customer-managed keys and may only post to websocket connections. The MCP core moves to lambda/shared/mcp with re-export shims. The runtime role is unchanged.
… grants are active
…ve JWT would replace
89d4922 to
ef53c76
Compare
|
Tool permissions. Not in this PR. With Rebase. The stack is rebased on
I also documented the Gateway trust boundary in |
In short
Opt-in path that serves the built-in
aidlcMCP through AgentCore Gateway instead of inside the agent's microVM (enable_mcp_gateway, defaultfalse, requiresnetwork_mode = "VPC").AWS_IAMGateway. The CLIs keep the sameaidlcserver and tool names.lambda/mcp-tools) runs the existing graph and process handlers.v2-orchestratoranddiscussionssign an ES256 grant with an asymmetric KMS key. The key policy allowskms:Signonly to those roles; the tools Lambda holds onlykms:GetPublicKey.MCP core modules move to
lambda/shared/mcp/; re-export shims inlambda/agentcore/mcp/keep existing imports working.Scope
First of four PRs: this one, then engine-data, then the credential broker and source-control alias, then the removal of the runtime role's direct data permissions. This PR changes where the built-in tools run and how each call is authorized. It does not reduce the runtime role's permissions on its own; the fourth PR removes them once the platform runs in strict mode.
Deliberately not included: an agent or persona identity in the grant. The persona is known only to the runtime, so an unsigned value would be forgeable by a compromised agent and could not be relied on for authorization or audit. Per-persona grants make sense together with CLI sandboxing and are a follow-up.
Accepted residual risks:
Validation
main. Full backend suite passes on this branch alone (6168 tests) and on the stack head;test:releasepasses, including the new active-execution check tests.format:check,dep:check,terraform fmt,terraform validateand the CI Terraform tests pass.Upgrade notes
With the flag off, every new resource is
count-gated and the grant key env var is not set. Enabling it creates the Gateway, target, tools Lambda, KMS key, security group and schema object, and switches the runtime to the proxy. The new roles followiam_role_pathandiam_permissions_boundary_arn.Executions started before the flag have no grant issuer and would fail the next time they invoke the runtime.
deploy-terraform.shtherefore refuses a plan that switches the runtime to the Gateway while any execution is started, running or parked, and lists them. Stop starting intents until the deployment has succeeded, let running executions park or finish, cancel the parked and queued ones, apply, then rewind each to its current stage (its first stage if it had not reached one): the rewind relaunches it with grants.AIDLC_ALLOW_ACTIVE_EXECUTIONS=1applies anyway. A directterraform applyskips the check.