mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-23 02:23:33 +08:00
fix(gateguard): rewrite routineBashMsg to use fact-presentation pattern (#1531)
* fix(gateguard): rewrite routineBashMsg to use fact-presentation pattern The imperative 'Quote user's instruction verbatim. Then retry.' phrasing triggers Claude Code's runtime anti-prompt-injection filter, deadlocking the first Bash call of every session. The sibling gates (edit, write, destructive) use multi-point fact-list framing that the runtime accepts. Align routineBashMsg with that pattern to restore the gate's intended behavior without changing run(), state schema, or any public API. Closes #1530 * docs(gateguard): sync SKILL.md routine gate spec with new message format CodeRabbit flagged that skills/gateguard/SKILL.md still described the pre-fix imperative message. Update the Routine Bash Gate section to match the numbered fact-list format used by the new routineBashMsg().
This commit is contained in:
@@ -62,13 +62,7 @@ function hashSessionKey(prefix, value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resolveSessionKey(data) {
|
function resolveSessionKey(data) {
|
||||||
const directCandidates = [
|
const directCandidates = [data && data.session_id, data && data.sessionId, data && data.session && data.session.id, process.env.CLAUDE_SESSION_ID, process.env.ECC_SESSION_ID];
|
||||||
data && data.session_id,
|
|
||||||
data && data.sessionId,
|
|
||||||
data && data.session && data.session.id,
|
|
||||||
process.env.CLAUDE_SESSION_ID,
|
|
||||||
process.env.ECC_SESSION_ID,
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const candidate of directCandidates) {
|
for (const candidate of directCandidates) {
|
||||||
const sanitized = sanitizeSessionKey(candidate);
|
const sanitized = sanitizeSessionKey(candidate);
|
||||||
@@ -101,12 +95,18 @@ function loadState() {
|
|||||||
const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
|
const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
|
||||||
const lastActive = state.last_active || 0;
|
const lastActive = state.last_active || 0;
|
||||||
if (Date.now() - lastActive > SESSION_TIMEOUT_MS) {
|
if (Date.now() - lastActive > SESSION_TIMEOUT_MS) {
|
||||||
try { fs.unlinkSync(stateFile); } catch (_) { /* ignore */ }
|
try {
|
||||||
|
fs.unlinkSync(stateFile);
|
||||||
|
} catch (_) {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
return { checked: [], last_active: Date.now() };
|
return { checked: [], last_active: Date.now() };
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
} catch (_) { /* ignore */ }
|
} catch (_) {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
return { checked: [], last_active: Date.now() };
|
return { checked: [], last_active: Date.now() };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,7 +139,11 @@ function saveState(state) {
|
|||||||
fs.renameSync(tmpFile, stateFile);
|
fs.renameSync(tmpFile, stateFile);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error && (error.code === 'EEXIST' || error.code === 'EPERM')) {
|
if (error && (error.code === 'EEXIST' || error.code === 'EPERM')) {
|
||||||
try { fs.unlinkSync(stateFile); } catch (_) { /* ignore */ }
|
try {
|
||||||
|
fs.unlinkSync(stateFile);
|
||||||
|
} catch (_) {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
fs.renameSync(tmpFile, stateFile);
|
fs.renameSync(tmpFile, stateFile);
|
||||||
} else {
|
} else {
|
||||||
throw error;
|
throw error;
|
||||||
@@ -147,7 +151,11 @@ function saveState(state) {
|
|||||||
}
|
}
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
if (tmpFile) {
|
if (tmpFile) {
|
||||||
try { fs.unlinkSync(tmpFile); } catch (_) { /* ignore */ }
|
try {
|
||||||
|
fs.unlinkSync(tmpFile);
|
||||||
|
} catch (_) {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -186,7 +194,9 @@ function isChecked(key) {
|
|||||||
// Ignore files that disappear between readdir/stat/unlink.
|
// Ignore files that disappear between readdir/stat/unlink.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (_) { /* ignore */ }
|
} catch (_) {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// --- Sanitize file path against injection ---
|
// --- Sanitize file path against injection ---
|
||||||
@@ -198,13 +208,15 @@ function sanitizePath(filePath) {
|
|||||||
const code = char.codePointAt(0);
|
const code = char.codePointAt(0);
|
||||||
const isAsciiControl = code <= 0x1f || code === 0x7f;
|
const isAsciiControl = code <= 0x1f || code === 0x7f;
|
||||||
const isBidiOverride = (code >= 0x200e && code <= 0x200f) || (code >= 0x202a && code <= 0x202e) || (code >= 0x2066 && code <= 0x2069);
|
const isBidiOverride = (code >= 0x200e && code <= 0x200f) || (code >= 0x202a && code <= 0x202e) || (code >= 0x2066 && code <= 0x2069);
|
||||||
sanitized += (isAsciiControl || isBidiOverride) ? ' ' : char;
|
sanitized += isAsciiControl || isBidiOverride ? ' ' : char;
|
||||||
}
|
}
|
||||||
return sanitized.trim().slice(0, 500);
|
return sanitized.trim().slice(0, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeForMatch(value) {
|
function normalizeForMatch(value) {
|
||||||
return String(value || '').replace(/\\/g, '/').toLowerCase();
|
return String(value || '')
|
||||||
|
.replace(/\\/g, '/')
|
||||||
|
.toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
function isClaudeSettingsPath(filePath) {
|
function isClaudeSettingsPath(filePath) {
|
||||||
@@ -265,7 +277,7 @@ function editGateMsg(filePath) {
|
|||||||
'1. List ALL files that import/require this file (use Grep)',
|
'1. List ALL files that import/require this file (use Grep)',
|
||||||
'2. List the public functions/classes affected by this change',
|
'2. List the public functions/classes affected by this change',
|
||||||
'3. If this file reads/writes data files, show field names, structure, and date format (use redacted or synthetic values, not raw production data)',
|
'3. If this file reads/writes data files, show field names, structure, and date format (use redacted or synthetic values, not raw production data)',
|
||||||
'4. Quote the user\'s current instruction verbatim',
|
"4. Quote the user's current instruction verbatim",
|
||||||
'',
|
'',
|
||||||
'Present the facts, then retry the same operation.'
|
'Present the facts, then retry the same operation.'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
@@ -281,7 +293,7 @@ function writeGateMsg(filePath) {
|
|||||||
'1. Name the file(s) and line(s) that will call this new file',
|
'1. Name the file(s) and line(s) that will call this new file',
|
||||||
'2. Confirm no existing file serves the same purpose (use Glob)',
|
'2. Confirm no existing file serves the same purpose (use Glob)',
|
||||||
'3. If this file reads/writes data files, show field names, structure, and date format (use redacted or synthetic values, not raw production data)',
|
'3. If this file reads/writes data files, show field names, structure, and date format (use redacted or synthetic values, not raw production data)',
|
||||||
'4. Quote the user\'s current instruction verbatim',
|
"4. Quote the user's current instruction verbatim",
|
||||||
'',
|
'',
|
||||||
'Present the facts, then retry the same operation.'
|
'Present the facts, then retry the same operation.'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
@@ -295,7 +307,7 @@ function destructiveBashMsg() {
|
|||||||
'',
|
'',
|
||||||
'1. List all files/data this command will modify or delete',
|
'1. List all files/data this command will modify or delete',
|
||||||
'2. Write a one-line rollback procedure',
|
'2. Write a one-line rollback procedure',
|
||||||
'3. Quote the user\'s current instruction verbatim',
|
"3. Quote the user's current instruction verbatim",
|
||||||
'',
|
'',
|
||||||
'Present the facts, then retry the same operation.'
|
'Present the facts, then retry the same operation.'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
@@ -305,8 +317,12 @@ function routineBashMsg() {
|
|||||||
return [
|
return [
|
||||||
'[Fact-Forcing Gate]',
|
'[Fact-Forcing Gate]',
|
||||||
'',
|
'',
|
||||||
'Quote the user\'s current instruction verbatim.',
|
'Before the first Bash command this session, present these facts:',
|
||||||
'Then retry the same operation.'
|
'',
|
||||||
|
'1. The current user request in one sentence',
|
||||||
|
'2. What this specific command verifies or produces',
|
||||||
|
'',
|
||||||
|
'Present the facts, then retry the same operation.'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,7 +356,7 @@ function run(rawInput) {
|
|||||||
const rawToolName = data.tool_name || '';
|
const rawToolName = data.tool_name || '';
|
||||||
const toolInput = data.tool_input || {};
|
const toolInput = data.tool_input || {};
|
||||||
// Normalize: case-insensitive matching via lookup map
|
// Normalize: case-insensitive matching via lookup map
|
||||||
const TOOL_MAP = { 'edit': 'Edit', 'write': 'Write', 'multiedit': 'MultiEdit', 'bash': 'Bash' };
|
const TOOL_MAP = { edit: 'Edit', write: 'Write', multiedit: 'MultiEdit', bash: 'Bash' };
|
||||||
const toolName = TOOL_MAP[rawToolName.toLowerCase()] || rawToolName;
|
const toolName = TOOL_MAP[rawToolName.toLowerCase()] || rawToolName;
|
||||||
|
|
||||||
if (toolName === 'Edit' || toolName === 'Write') {
|
if (toolName === 'Edit' || toolName === 'Write') {
|
||||||
|
|||||||
@@ -84,7 +84,8 @@ Triggers on: `rm -rf`, `git reset --hard`, `git push --force`, `drop table`, etc
|
|||||||
### Routine Bash Gate (once per session)
|
### Routine Bash Gate (once per session)
|
||||||
|
|
||||||
```
|
```
|
||||||
Quote the user's current instruction verbatim.
|
1. The current user request in one sentence
|
||||||
|
2. What this specific command verifies or produces
|
||||||
```
|
```
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|||||||
Reference in New Issue
Block a user