diff --git a/hooks/scripts/store-memories.js b/hooks/scripts/store-memories.js
index 6924902..c91a203 100755
--- a/hooks/scripts/store-memories.js
+++ b/hooks/scripts/store-memories.js
@@ -92,6 +92,12 @@ try {
return text && text.trim().length > 0;
}
+ function redactSensitiveUrls(text) {
+ if (!text) return text;
+
+ return text.replace(/\bhttps?:\/\/(localhost|127\.0\.0\.1|\[::1\])(?::\d+)?(?:\/[^\s"'`]*)?/gi, '[LOCAL_URL_REDACTED]');
+ }
+
/**
* Extract the last turn's user input and assistant response
*
@@ -192,8 +198,8 @@ try {
// Extract the last turn's content
const lastTurn = extractLastTurn(lines);
- const lastUser = lastTurn.user;
- const lastAssistant = lastTurn.assistant;
+ const lastUser = redactSensitiveUrls(lastTurn.user);
+ const lastAssistant = redactSensitiveUrls(lastTurn.assistant);
debug('extracted:', {
userLength: lastUser?.length || 0,
diff --git a/scripts/test-redact-sensitive-urls.js b/scripts/test-redact-sensitive-urls.js
new file mode 100644
index 0000000..b67bf6d
--- /dev/null
+++ b/scripts/test-redact-sensitive-urls.js
@@ -0,0 +1,65 @@
+#!/usr/bin/env node
+
+import { mkdtempSync, writeFileSync, readFileSync, rmSync, existsSync } from 'fs';
+import { tmpdir } from 'os';
+import { join } from 'path';
+import { spawnSync } from 'child_process';
+
+const pluginRoot = '/Users/deepzen/projects/evermem-claude-code';
+const storeScript = join(pluginRoot, 'hooks/scripts/store-memories.js');
+const tempDir = mkdtempSync(join(tmpdir(), 'evermem-redact-'));
+const transcriptPath = join(tempDir, 'transcript.jsonl');
+const debugLogPath = '/tmp/evermem-debug.log';
+
+const transcript = [
+ JSON.stringify({
+ type: 'user',
+ message: {
+ content: 'evermem:hub\n/evermem:hub\nOpen http://localhost:3456/?key=${EVERMEM_API_KEY}'
+ }
+ }),
+ JSON.stringify({
+ type: 'assistant',
+ message: {
+ content: [
+ { type: 'text', text: 'Memory Hub 服务已启动。\n\nhttp://localhost:3456/?key=fake-secret-key' }
+ ]
+ }
+ })
+].join('\n');
+
+writeFileSync(transcriptPath, transcript + '\n', 'utf8');
+writeFileSync(debugLogPath, '', 'utf8');
+
+const result = spawnSync('node', [storeScript], {
+ input: JSON.stringify({ transcript_path: transcriptPath, cwd: '/Users/deepzen' }),
+ encoding: 'utf8',
+ env: {
+ ...process.env,
+ EVERMEM_DEBUG: '1',
+ EVERMEM_API_KEY: process.env.EVERMEM_API_KEY || ''
+ }
+});
+
+const stdout = result.stdout || '';
+const debugLog = existsSync(debugLogPath) ? readFileSync(debugLogPath, 'utf8') : '';
+
+try {
+ if (stdout.includes('FAILED (403)')) {
+ console.error('Expected sensitive URL content to be redacted before save, but save still failed with 403.');
+ if (debugLog) console.error(debugLog);
+ process.exit(1);
+ }
+
+ const leakedLocalUrlPattern = /https?:\/\/(localhost|127\.0\.0\.1|\[::1\])/i;
+ if (leakedLocalUrlPattern.test(debugLog)) {
+ console.error('Expected debug log to avoid leaking raw localhost URLs.');
+ console.error(debugLog);
+ process.exit(1);
+ }
+
+ console.log('Sensitive URL content was redacted before save.');
+ process.exit(0);
+} finally {
+ rmSync(tempDir, { recursive: true, force: true });
+}