feat: add Lambda interceptors for AgentCore gateways#1330
Conversation
Package TarballHow to installgh release download pr-1330-tarball --repo aws/agentcore-cli --pattern "*.tgz" --dir /tmp/pr-tarball
npm install -g /tmp/pr-tarball/aws-agentcore-0.14.1.tgz |
|
Claude Security Review: no high-confidence findings. (run) |
Adds end-to-end support for Lambda interceptors at gateway REQUEST and RESPONSE points: a new InterceptorPrimitive, three vended templates (pass-through, jwt-scope-authorizer, tools-list-filter) for both Python and Node.js, deploy-side preflight, and `logs interceptor` / `invoke interceptor` verbs. Primitive surface ----------------- - `agentcore add interceptor --name --gateway --interception-points --template --runtime [--timeout] [--no-pass-request-headers] [--additional-policies]` for managed mode - External mode via direct `agentcore.json` edit referencing a customer-provided unqualified Lambda ARN - `agentcore remove interceptor --name` reconciles deploy state on next `agentcore deploy` - `agentcore logs interceptor --name [--follow|--since|--until]` tails CloudWatch for managed interceptors; external interceptors short- circuit to a remediation message pointing at `aws logs tail` - `agentcore invoke interceptor --name [--payload]` invokes the underlying Lambda for managed interceptors with the same external- mode remediation message Schema ------ - `interceptors` array on `AgentCoreProjectSpec` with cross-field superRefine for cardinality (max 1 per point per gateway, max 2 per gateway) and gateway-name reference checks - Lambda ARN regex tightened to reject version/alias qualifiers - `passRequestHeaders`, `entrypoint`, `timeoutSeconds`, `runtime` are optional with consumption-time defaults so user-edited `agentcore.json` stays sparse across CLI round-trips Deploy ------ - `validateInterceptors()` checks managed-mode codeLocation existence and emits a masked cross-account WARN for external mode (with full `aws lambda add-permission` remediation snippet); also wired into `agentcore validate` so the pre-check works without invoking deploy - `validateGatewayTargetLambdas()` does a best-effort `lambda:GetFunction` per `lambda-function-arn` target and WARNs on any failure (NotFound, AccessDenied, throttle) so a typo'd ARN surfaces before CFN ROLLBACK - Account IDs go through `maskAccountId()` for all user-visible warning output (PII masking) Templates --------- - `pass-through`, `jwt-scope-authorizer`, `tools-list-filter` for Python (`pyproject.toml` + hatchling) and Node.js (`index.mjs` + `package.json`) - `PackageName` Handlebars var for NPM-safe lowercase package naming Telemetry --------- - `has_cross_account_warning` boolean on the InterceptorPrimitive command-run telemetry schema
ink (via cli-cursor) writes \x1b[?25h to its render stream on App unmount. The cli-cursor isTTY check was passing because ink's stdout wrapper inherits isTTY from the underlying stream object, even when stdout is piped or redirected. The trailing escape corrupted machine-parseable output for every CLI invocation that captured stdout — most visibly --json (broke JSON parsing) but also any pipe/redirect workflow. Found by the round-2 bug bash (E6) when an agent tried to `json.load()` the output of `agentcore add interceptor --json` and got "Extra data" — the bytes after the JSON were the cursor-show escape. Fix: at the CLI entry, wrap process.stdout and process.stderr writes to strip \x1b[?25[hl] (cursor show / hide) when the stream is NOT a TTY. Real TTYs are untouched so interactive terminal behavior — cursor hiding during spinners, restoring on exit — keeps working. Verified live with the bundled CLI: - agentcore --version: ends with newline only, no escape - agentcore validate: ends with newline only - agentcore add gateway --json: produces valid JSON, json.load() succeeds
a2daa82 to
dc2fe62
Compare
|
Claude Security Review: the review run failed before completing. See the run for details. |
The 'Limitations / out of scope (P0)' section captured deferred features for internal review context, not customer-facing guidance. Move it to the PR description so reviewers still see what was deliberately excluded.
|
Claude Security Review: the review run failed before completing. See the run for details. |
Three fixes for CI failures on the open PR: - schemas/agentcore.schema.v1.json: revert to origin/main. The CI gate rejects any change to this file because it is regenerated and released automatically — my rebase had carried my staged version. - .prettierignore: add `src/assets/**/*.mjs`. The new Node interceptor templates ship as raw .mjs that's vended to users; we shouldn't reformat them via prettier on the way in. - npm-shrinkwrap.json: revert to origin/main. My local `npm install` during the rebase produced a different ordering than what's on upstream — keeping upstream's authoritative version avoids drift.
|
Claude Security Review: the review run failed before completing. See the run for details. |
Four unit tests asserted against pre-FIX-BATCH-3 behavior. Bringing them in line with the shipped code: 1. logs/__tests__/interceptor.test.ts — error message no longer mentions the internal field name `interceptorFunctionName`. Assert against the new "has no Lambda function" copy that the user actually sees. 2. shared/__tests__/interceptor-mode-check.test.ts — error message no longer leaks `deployed-state.json`. Assert against the new "is not deployed" copy. 3. schema/.../interceptor.test.ts — schema fields are .optional() and defaults are applied at consumption time (not parse time), so a sparse parse leaves them undefined rather than filling them in. 4. operations/deploy/__tests__/preflight.test.ts — the new validateGatewayTargetLambdas() preflight imports getCredentialProvider from aws/account.js. Stub it in the existing vi.mock so existing fixtures (which have no lambda-function-arn targets) keep working.
|
Claude Security Review: the review run failed before completing. See the run for details. |
The vended CDK project at src/assets/cdk/ runs against whichever @aws/agentcore-cdk version the user installs from npm. Until the CDK PR merges and publishes a new alpha, the registry version doesn't declare `interceptors` on the project spec type — so the e2e job fails with TS2353 when building the vended cdk/ project. Drop the explicit `interceptors: []` from the test object literal. Zod's default fills in `[]` at parse time when the new CDK ships, and the field's absence is harmless in the meantime. The synth assertion itself doesn't care about interceptors at all. Snapshot updated to match.
|
Claude Security Review: the review run failed before completing. See the run for details. |
tejaskash
left a comment
There was a problem hiding this comment.
Big PR but tight execution — the schema/CDK mirror, account masking, structured external-mode remediation, and cursor-escape fix are all well-handled. Four inline notes; none are blocking.
| try { | ||
| const project = await this.readProjectSpec(); | ||
| project.interceptors = project.interceptors.filter(i => i.name !== options.name); | ||
| await this.writeProjectSpec(project); |
There was a problem hiding this comment.
Rollback only un-edits agentcore.json — if renderInterceptorTemplate throws after partial writes, the half-rendered files in app/<name>/ stay on disk. A subsequent agentcore add interceptor --name <same> then fails at scaffolding because the directory exists. Either rm the partial tree on rollback, or document that the user has to clean it up before retrying.
| error: new ValidationError(`Cannot read --payload-file "${options.payloadFile}": ${msg}`), | ||
| }; | ||
| } | ||
| } else if (options.payload) { |
There was a problem hiding this comment.
--payload and --payload-file aren't mutually exclusive — passing both silently prefers --payload-file. Most CLIs reject the combo with a clear error. A user passing both probably has a bug they'd want to know about.
| } | ||
|
|
||
| try { | ||
| await client.send(new GetFunctionCommand({ FunctionName: arn })); |
There was a problem hiding this comment.
Sequential GetFunction across all gateways × targets. For projects with many targets (and especially across regions, since each region gets its own client), the latency adds up on every deploy. Promise.all over a flattened (gateway, target) list would parallelize without changing the warn-only semantics — bonus: a single SDK throttle event won't serialize the rest.
| return originalWrite(chunk as never, ...(rest as [never])); | ||
| }) as typeof stream.write; | ||
| } | ||
| patchStream(process.stdout); |
There was a problem hiding this comment.
Module-import side effect that monkey-patches process.stdout.write and process.stderr.write for the lifetime of the process. Scope is correct (non-TTY only, just \x1b[?25[hl]) and the motivation is real, but it's the kind of thing a future contributor will be very surprised by. Worth either moving to an explicit installCursorFilter() called from main() so it's greppable, or making the comment block more prominent so it doesn't read like a stray import-time bootstrap.
Description
Adds end-to-end support for Lambda interceptors at gateway REQUEST and RESPONSE points: a new
InterceptorPrimitive, three vended templates (pass-through,jwt-scope-authorizer,tools-list-filter) for both Python and Node.js, deploy-side preflight, andlogs interceptor/invoke interceptorverbs.Primitive surface
Schema
Deploy
Templates
Bug fixes
Out of scope (P0)
The following were deliberately excluded from this PR. None are bugs — flagged here so reviewers don't ask "where is X":
Related Issue
Closes #
Documentation PR
Type of Change
Testing
Additionally:
Checklist
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.