Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions src/cli/aws/agentcore-control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,8 +409,10 @@ export async function getMemoryDetail(options: GetMemoryOptions): Promise<Memory

const tags = await fetchTags(client, memory.arn, 'memory');

const rawKeys = memory.indexedKeys;
const indexedKeys = rawKeys?.flatMap(k => {
const rawKeys = (memory as unknown as Record<string, unknown>).indexedKeys as
| { key?: string; type?: string }[]
| undefined;
const indexedKeys = rawKeys?.flatMap((k: { key?: string; type?: string }) => {
if (!k.key || !k.type) {
console.warn(`Warning: Skipping malformed indexed key from API response: ${JSON.stringify(k)}`);
return [];
Expand Down Expand Up @@ -561,7 +563,7 @@ export async function getEvaluator(options: GetEvaluatorOptions): Promise<GetEva
status: response.status ?? 'UNKNOWN',
description: response.description,
evaluatorConfig,
kmsKeyArn: response.kmsKeyArn,
kmsKeyArn: (response as unknown as Record<string, unknown>).kmsKeyArn as string | undefined,
tags,
};
}
Expand Down
50 changes: 50 additions & 0 deletions src/cli/commands/invoke/__tests__/redact-sensitive-text.test.ts
Original file line number Diff line number Diff line change
@@ -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]');
});
});
23 changes: 16 additions & 7 deletions src/cli/commands/invoke/command.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,22 @@
}
throw err;
}
}

Check warning on line 88 in src/cli/commands/invoke/command.tsx

View workflow job for this annotation

GitHub Actions / lint

Unsafe Regular Expression

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]');
}
Comment thread
notgitika marked this conversation as resolved.

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}`);
Expand All @@ -100,11 +109,10 @@
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}`);
Expand Down Expand Up @@ -344,10 +352,11 @@
});
}
} 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(<Text color="red">Error: {getErrorMessage(error)}</Text>);
render(<Text color="red">Error: {msg}</Text>);
}
process.exit(1);
}
Expand Down
Loading