add check for userPromptSubmit

This commit is contained in:
DennisYu07 2026-03-27 11:27:33 +08:00
parent cf0b67ef8e
commit 0c6b16c695
4 changed files with 151 additions and 4 deletions

View file

@ -6,7 +6,10 @@
import type React from 'react';
import { useMemo } from 'react';
import { escapeAnsiCtrlCodes } from '../utils/textUtils.js';
import {
escapeAnsiCtrlCodes,
sanitizeSensitiveText,
} from '../utils/textUtils.js';
import type { HistoryItem } from '../types.js';
import {
UserMessage,
@ -233,7 +236,7 @@ const HistoryItemDisplayComponent: React.FC<HistoryItemDisplayProps> = ({
)}
{itemForDisplay.type === 'user_prompt_submit_blocked' && (
<ErrorMessage
text={`UserPromptSubmit operation blocked by hook:\n${itemForDisplay.reason}\n\nOriginal prompt: ${itemForDisplay.originalPrompt}`}
text={`UserPromptSubmit operation blocked by hook:\n${itemForDisplay.reason}\n\nOriginal prompt: ${sanitizeSensitiveText(itemForDisplay.originalPrompt)}`}
/>
)}
{itemForDisplay.type === 'stop_hook_loop' && (

View file

@ -9,7 +9,7 @@ import type {
ToolCallConfirmationDetails,
ToolEditConfirmationDetails,
} from '@qwen-code/qwen-code-core';
import { escapeAnsiCtrlCodes } from './textUtils.js';
import { escapeAnsiCtrlCodes, sanitizeSensitiveText } from './textUtils.js';
describe('textUtils', () => {
describe('escapeAnsiCtrlCodes', () => {
@ -167,4 +167,71 @@ describe('textUtils', () => {
});
});
});
describe('sanitizeSensitiveText', () => {
it('should return text unchanged if no sensitive patterns', () => {
const text = 'Hello, this is a normal prompt';
expect(sanitizeSensitiveText(text)).toBe(text);
});
it('should redact OpenAI-style API keys', () => {
const text = 'Use API key sk-1234567890abcdefghijklmnopqrstuv for access';
expect(sanitizeSensitiveText(text)).toBe(
'Use API key sk-***REDACTED*** for access',
);
});
it('should redact api_key assignments', () => {
const text = 'api_key=supersecretkey123456789012';
expect(sanitizeSensitiveText(text)).toBe('api_key=***REDACTED***');
});
it('should redact Bearer tokens', () => {
const text = 'Authorization: Bearer abc123token456xyz';
expect(sanitizeSensitiveText(text)).toBe(
'Authorization: Bearer ***REDACTED***',
);
});
it('should redact password assignments', () => {
const text = 'password=mysecretpassword123';
expect(sanitizeSensitiveText(text)).toBe('password=***REDACTED***');
});
it('should redact AWS access keys', () => {
const text = 'AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE';
expect(sanitizeSensitiveText(text)).toBe(
'AWS_ACCESS_KEY_ID=***REDACTED***',
);
});
it('should truncate long text', () => {
const text = 'a'.repeat(300);
const result = sanitizeSensitiveText(text, 200);
expect(result.length).toBe(200);
expect(result.endsWith('...')).toBe(true);
});
it('should handle custom max length', () => {
const text =
'This is a test prompt with sk-1234567890abcdefghijklmnopqrstuv';
const result = sanitizeSensitiveText(text, 20);
expect(result.length).toBe(20);
expect(result).toBe('This is a test pr...');
});
it('should handle empty string', () => {
expect(sanitizeSensitiveText('')).toBe('');
});
it('should redact multiple sensitive patterns', () => {
const text =
'api_key=secretkey12345678901234 and password=mypass123 and sk-test123456789012345678901';
const result = sanitizeSensitiveText(text);
expect(result).toContain('***REDACTED***');
expect(result).not.toContain('secretkey12345678901234');
expect(result).not.toContain('mypass123');
expect(result).not.toContain('sk-test123456789012345678901');
});
});
});

View file

@ -214,3 +214,77 @@ export function escapeAnsiCtrlCodes<T>(obj: T): T {
return newObj !== null ? newObj : obj;
}
/**
* Patterns that may indicate sensitive information like API keys, tokens, passwords.
*/
const SENSITIVE_PATTERNS: Array<{ pattern: RegExp; replacement: string }> = [
// API keys with common prefixes
{
pattern: /(sk-[a-zA-Z0-9]{20,})/g,
replacement: 'sk-***REDACTED***',
},
{
pattern: /(api[_-]?key[_-]?[=:]\s*)[a-zA-Z0-9_-]{20,}/gi,
replacement: '$1***REDACTED***',
},
// Bearer tokens
{
pattern: /(Bearer\s+)[a-zA-Z0-9._-]+/gi,
replacement: '$1***REDACTED***',
},
// Generic tokens
{
pattern: /(token[_-]?[=:]\s*)[a-zA-Z0-9._-]{10,}/gi,
replacement: '$1***REDACTED***',
},
// Passwords in connection strings or assignments
{
pattern: /(password[_-]?[=:]\s*)[^\s]+/gi,
replacement: '$1***REDACTED***',
},
{
pattern: /(pwd[_-]?[=:]\s*)[^\s]+/gi,
replacement: '$1***REDACTED***',
},
// AWS keys
{
pattern: /(AKIA[A-Z0-9]{16})/g,
replacement: '***REDACTED***',
},
// Generic secret patterns
{
pattern: /(secret[_-]?[=:]\s*)[a-zA-Z0-9._-]{10,}/gi,
replacement: '$1***REDACTED***',
},
];
/**
* Sanitizes text by redacting potentially sensitive information like API keys,
* tokens, and passwords. Also truncates long text to a maximum length.
*
* @param text The text to sanitize
* @param maxLength Maximum length of the output text (default: 200)
* @returns Sanitized and truncated text
*/
export function sanitizeSensitiveText(
text: string,
maxLength: number = 200,
): string {
let result = text;
// Apply each sensitive pattern replacement
for (const { pattern, replacement } of SENSITIVE_PATTERNS) {
result = result.replace(pattern, replacement);
}
// Truncate if too long
if (result.length > maxLength) {
if (maxLength <= 3) {
return result.slice(0, maxLength);
}
return result.slice(0, maxLength - 3) + '...';
}
return result;
}