diff --git a/src/cli/commands/invoke/__tests__/redact-sensitive-text.test.ts b/src/cli/commands/invoke/__tests__/redact-sensitive-text.test.ts
new file mode 100644
index 000000000..1137aa2eb
--- /dev/null
+++ b/src/cli/commands/invoke/__tests__/redact-sensitive-text.test.ts
@@ -0,0 +1,50 @@
+import { redactSensitiveText } from '../command.js';
+import { describe, expect, it } from 'vitest';
+
+describe('redactSensitiveText', () => {
+ it('redacts Bearer tokens', () => {
+ expect(redactSensitiveText('Authorization: Bearer eyJhbGciOiJSUzI1NiJ9.payload.sig')).toBe(
+ 'Authorization: Bearer [REDACTED]'
+ );
+ });
+
+ it('redacts Bearer tokens in JSON', () => {
+ expect(redactSensitiveText('{"header":"Bearer eyJhbGciOiJSUzI1NiJ9.payload.sig"}')).toBe(
+ '{"header":"Bearer [REDACTED]"}'
+ );
+ });
+
+ it('redacts client_secret in key=value form', () => {
+ expect(redactSensitiveText('client_secret=abc123def')).toBe('client_secret=[REDACTED]');
+ });
+
+ it('redacts client_secret in JSON form', () => {
+ expect(redactSensitiveText('{"client_secret":"abc123def"}')).toBe('{"client_secret":"[REDACTED]"}');
+ });
+
+ it('redacts token in key=value form', () => {
+ expect(redactSensitiveText('token=eyJhbGciOiJSUzI1NiJ9')).toBe('token=[REDACTED]');
+ });
+
+ it('redacts token in JSON form', () => {
+ expect(redactSensitiveText('{"token":"eyJhbGciOiJSUzI1NiJ9"}')).toBe('{"token":"[REDACTED]"}');
+ });
+
+ it('redacts access_token in JSON form', () => {
+ expect(redactSensitiveText('{"access_token":"jwt.token.here"}')).toBe('{"access_token":"[REDACTED]"}');
+ });
+
+ it('redacts client-secret with hyphen', () => {
+ expect(redactSensitiveText('client-secret=mysecret')).toBe('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]');
+ });
+});
diff --git a/src/cli/commands/invoke/command.tsx b/src/cli/commands/invoke/command.tsx
index 0a847eab2..ba1a230a9 100644
--- a/src/cli/commands/invoke/command.tsx
+++ b/src/cli/commands/invoke/command.tsx
@@ -49,7 +49,7 @@ async function handleInvokeCLI(options: InvokeOptions, preloadedContext?: Invoke
if (isPreviewEnabled() && options.harnessArn) {
const region = options.region ?? process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION;
if (!region) {
- const msg = '--region is required with --harness-arn (or set AWS_REGION)';
+ const msg = redactSensitiveText('--region is required with --harness-arn (or set AWS_REGION)');
if (options.json) {
console.log(JSON.stringify({ success: false, error: msg }));
} else {
@@ -87,11 +87,20 @@ 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]');
+}
+
function printInvokeResult(result: InvokeResult, options: InvokeOptions): void {
if (options.json) {
- console.log(JSON.stringify(serializeResult(result)));
+ const serialized = serializeResult(result);
+ if (typeof serialized.response === 'string') serialized.response = redactSensitiveText(serialized.response);
+ if (typeof serialized.error === 'string') serialized.error = redactSensitiveText(serialized.error);
+ console.log(JSON.stringify(serialized));
} else if (options.stream) {
- // Streaming already wrote to stdout, just show session and log path
if (result.sessionId) {
console.error(`\nSession: ${result.sessionId}`);
console.error(`To resume: agentcore invoke --session-id ${result.sessionId}`);
@@ -100,11 +109,10 @@ function printInvokeResult(result: InvokeResult, options: InvokeOptions): void {
console.error(`Log: ${result.logFilePath}`);
}
} else {
- // Non-streaming, non-json: print provider info and response or error
if (result.success && result.response) {
- console.log(result.response);
+ console.log(redactSensitiveText(result.response));
} else if (!result.success && result.error) {
- console.error(result.error.message);
+ console.error(redactSensitiveText(result.error.message));
}
if (result.sessionId) {
console.error(`\nSession: ${result.sessionId}`);
@@ -344,10 +352,11 @@ export const registerInvoke = (program: Command) => {
});
}
} catch (error) {
+ const msg = redactSensitiveText(getErrorMessage(error));
if (cliOptions.json) {
- console.log(JSON.stringify({ success: false, error: getErrorMessage(error) }));
+ console.log(JSON.stringify({ success: false, error: msg }));
} else {
- render(Error: {getErrorMessage(error)});
+ render(Error: {msg});
}
process.exit(1);
}