From ab6494fd4497e386ce51f66e1cc9d906c15cfc85 Mon Sep 17 00:00:00 2001 From: Local E2E Date: Mon, 8 Jun 2026 18:41:46 +0000 Subject: [PATCH] fix(invoke): adjust redaction regex to allow words following bearer --- npm-shrinkwrap.json | 11 +++-- package.json | 1 + .../__tests__/redact-sensitive-text.test.ts | 48 +++++++++++++++---- src/cli/commands/invoke/command.tsx | 14 ++++-- 4 files changed, 55 insertions(+), 19 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 4d38fb6ff..4f6fb76a6 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "@aws/agentcore", - "version": "0.17.0", + "version": "0.18.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@aws/agentcore", - "version": "0.17.0", + "version": "0.18.0", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -81,6 +81,7 @@ "eslint-plugin-security": "^4.0.0", "husky": "^9.1.7", "ink-testing-library": "^4.0.0", + "jose": "^6.2.3", "lint-staged": "^16.2.7", "node-pty": "^1.1.0", "prettier": "^3.7.4", @@ -10904,9 +10905,9 @@ "license": "MIT" }, "node_modules/jose": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.2.tgz", - "integrity": "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", "dev": true, "license": "MIT", "funding": { diff --git a/package.json b/package.json index ebe97f13c..af342f7f4 100644 --- a/package.json +++ b/package.json @@ -147,6 +147,7 @@ "eslint-plugin-security": "^4.0.0", "husky": "^9.1.7", "ink-testing-library": "^4.0.0", + "jose": "^6.2.3", "lint-staged": "^16.2.7", "node-pty": "^1.1.0", "prettier": "^3.7.4", diff --git a/src/cli/commands/invoke/__tests__/redact-sensitive-text.test.ts b/src/cli/commands/invoke/__tests__/redact-sensitive-text.test.ts index 1137aa2eb..c01fd12ab 100644 --- a/src/cli/commands/invoke/__tests__/redact-sensitive-text.test.ts +++ b/src/cli/commands/invoke/__tests__/redact-sensitive-text.test.ts @@ -1,17 +1,34 @@ import { redactSensitiveText } from '../command.js'; -import { describe, expect, it } from 'vitest'; +import { SignJWT } from 'jose'; +import { beforeAll, describe, expect, it } from 'vitest'; + +const TEST_SIGNING_SECRET = new TextEncoder().encode('redaction-unit-test-signing-secret-0123456789'); + +async function makeJwt(claims: Record = { sub: '1234567890', aud: 'client-abc' }): Promise { + return new SignJWT(claims) + .setProtectedHeader({ alg: 'HS256', typ: 'JWT' }) + .setIssuedAt() + .setExpirationTime('1h') + .sign(TEST_SIGNING_SECRET); +} describe('redactSensitiveText', () => { + let jwt: string; + + beforeAll(async () => { + jwt = await makeJwt(); + }); + it('redacts Bearer tokens', () => { - expect(redactSensitiveText('Authorization: Bearer eyJhbGciOiJSUzI1NiJ9.payload.sig')).toBe( - 'Authorization: Bearer [REDACTED]' - ); + expect(redactSensitiveText(`Authorization: Bearer ${jwt}`)).toBe('Authorization: Bearer [REDACTED]'); }); it('redacts Bearer tokens in JSON', () => { - expect(redactSensitiveText('{"header":"Bearer eyJhbGciOiJSUzI1NiJ9.payload.sig"}')).toBe( - '{"header":"Bearer [REDACTED]"}' - ); + expect(redactSensitiveText(`{"header":"Bearer ${jwt}"}`)).toBe('{"header":"Bearer [REDACTED]"}'); + }); + + it('redacts a JWT by shape even without a "bearer"/key prefix', () => { + expect(redactSensitiveText(`agent response: ${jwt}`)).toBe('agent response: [REDACTED]'); }); it('redacts client_secret in key=value form', () => { @@ -38,13 +55,24 @@ describe('redactSensitiveText', () => { expect(redactSensitiveText('client-secret=mysecret')).toBe('client-secret=[REDACTED]'); }); + it('handles multiple sensitive values in one string', () => { + expect(redactSensitiveText(`Bearer ${jwt} and client_secret=xyz789`)).toBe( + 'Bearer [REDACTED] and client_secret=[REDACTED]' + ); + }); + it('does not modify text without sensitive content', () => { const input = 'Agent responded successfully with 200 OK'; expect(redactSensitiveText(input)).toBe(input); }); - it('handles multiple sensitive values in one string', () => { - const input = 'Bearer abc123 and client_secret=xyz789'; - expect(redactSensitiveText(input)).toBe('Bearer [REDACTED] and client_secret=[REDACTED]'); + it('does not redact the literal word "token" after "bearer"', () => { + const input = "Agent 'E2eJwt123' is configured for CUSTOM_JWT but no bearer token is available."; + expect(redactSensitiveText(input)).toBe(input); + }); + + it('does not redact prose like "Invalid Bearer Token"', () => { + const input = 'Invalid Bearer Token'; + expect(redactSensitiveText(input)).toBe(input); }); }); diff --git a/src/cli/commands/invoke/command.tsx b/src/cli/commands/invoke/command.tsx index dce1e82bd..dfc2f3298 100644 --- a/src/cli/commands/invoke/command.tsx +++ b/src/cli/commands/invoke/command.tsx @@ -82,10 +82,16 @@ async function handleInvokeCLI(options: InvokeOptions, preloadedContext?: Invoke } export function redactSensitiveText(value: string): string { - return value - .replace(/(bearer\s+)[a-z0-9\-._~+/]+=*/gi, '$1[REDACTED]') - .replace(/(client[_-]?secret["']?\s*[:=]\s*["']?)([^"',\s}]+)/gi, '$1[REDACTED]') - .replace(/((?:access[_-]?)?token["']?\s*[:=]\s*["']?)([^"',\s}]+)/gi, '$1[REDACTED]'); + return ( + value + // AgentCore inbound bearer tokens are always CUSTOM_JWT/OIDC JWTs, and a JWT always begins + // with `eyJ` (base64url of `{"`, the start of its header/payload JSON). Matching by shape + // redacts the token wherever it appears and never touches prose like "bearer token". + // See https://stackoverflow.com/a/74181595 (why JWTs start with `eyJ`). + .replace(/eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g, '[REDACTED]') + .replace(/(client[_-]?secret["']?\s*[:=]\s*["']?)([^"',\s}]+)/gi, '$1[REDACTED]') + .replace(/((?:access[_-]?)?token["']?\s*[:=]\s*["']?)([^"',\s}]+)/gi, '$1[REDACTED]') + ); } function printInvokeResult(result: InvokeResult, options: InvokeOptions): void {