mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-06-16 16:36:53 +08:00
fix: prevent IOC scanner false positives on hook filenames and scan .cursor configs (#2245)
* fix: prevent IOC scanner false positives on hook filenames and scan .cursor configs The supply-chain IOC scanner matched CRITICAL_TEXT_INDICATORS with plain substring search, so legitimate hook filenames that merely end with a known payload name (e.g. the stock Cursor hook before-shell-execution.js vs the payload execution.js) were flagged as CRITICAL. Indicator matching now requires a non-filename character before the match. Also add .cursor/ to the special config paths so Cursor hooks.json files (a known persistence vector already listed in PERSISTENCE_FILENAMES) are actually inspected in normal checkouts - previously they were only scanned by accident when the repo path happened to contain /.claude/. * test: cover underscore-prefixed filenames in IOC boundary suppression Make explicit that '_' is treated as a filename word character, so snake_case hook names like post_execution.js are intentionally not flagged by the execution.js indicator (real payload references appear after '/', quotes, or whitespace).
This commit is contained in:
parent
d293941643
commit
e3f18d2376
@ -414,6 +414,7 @@ function normalizeForMatch(value) {
|
|||||||
function isInSpecialConfigPath(filePath) {
|
function isInSpecialConfigPath(filePath) {
|
||||||
const normalized = normalizedPath(filePath);
|
const normalized = normalizedPath(filePath);
|
||||||
return /\/\.claude\//.test(normalized)
|
return /\/\.claude\//.test(normalized)
|
||||||
|
|| /\/\.cursor\//.test(normalized)
|
||||||
|| /\/\.vscode\//.test(normalized)
|
|| /\/\.vscode\//.test(normalized)
|
||||||
|| /\/\.kiro\/settings\//.test(normalized)
|
|| /\/\.kiro\/settings\//.test(normalized)
|
||||||
|| /\/Library\/LaunchAgents\//.test(normalized)
|
|| /\/Library\/LaunchAgents\//.test(normalized)
|
||||||
@ -661,21 +662,26 @@ function scanFile(filePath, rootDir, findings) {
|
|||||||
|
|
||||||
for (const indicator of CRITICAL_TEXT_INDICATORS) {
|
for (const indicator of CRITICAL_TEXT_INDICATORS) {
|
||||||
const normalizedIndicator = normalizeForMatch(indicator);
|
const normalizedIndicator = normalizeForMatch(indicator);
|
||||||
let index = lowerText.indexOf(normalizedIndicator);
|
// Require a non-filename character before the indicator so legitimate
|
||||||
while (index !== -1) {
|
// names that merely end with an IOC filename (e.g. the stock Cursor hook
|
||||||
if (!indexInRanges(index, defensiveClaudeDenyRanges)) {
|
// `before-shell-execution.js` vs the payload `execution.js`) do not match.
|
||||||
|
const indicatorPattern = new RegExp(
|
||||||
|
`(?<![a-z0-9_-])${escapeRegExp(normalizedIndicator)}`,
|
||||||
|
'g',
|
||||||
|
);
|
||||||
|
let match;
|
||||||
|
while ((match = indicatorPattern.exec(lowerText)) !== null) {
|
||||||
|
if (!indexInRanges(match.index, defensiveClaudeDenyRanges)) {
|
||||||
addFinding(
|
addFinding(
|
||||||
findings,
|
findings,
|
||||||
'critical',
|
'critical',
|
||||||
relativePath,
|
relativePath,
|
||||||
lineForIndex(text, index),
|
lineForIndex(text, match.index),
|
||||||
indicator,
|
indicator,
|
||||||
'Known active supply-chain IOC is present',
|
'Known active supply-chain IOC is present',
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
index = lowerText.indexOf(normalizedIndicator, index + normalizedIndicator.length);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -310,6 +310,45 @@ function run() {
|
|||||||
});
|
});
|
||||||
})) passed++; else failed++;
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (test('does not flag legitimate hook filenames ending in an IOC filename', () => {
|
||||||
|
withFixture({
|
||||||
|
'.cursor/hooks.json': JSON.stringify({
|
||||||
|
hooks: {
|
||||||
|
beforeShellExecution: [{
|
||||||
|
command: 'node .cursor/hooks/before-shell-execution.js',
|
||||||
|
}],
|
||||||
|
afterShellExecution: [{
|
||||||
|
command: 'node .cursor/hooks/after-shell-execution.js',
|
||||||
|
}],
|
||||||
|
beforeMCPExecution: [{
|
||||||
|
command: 'node .cursor/hooks/before-mcp-execution.js',
|
||||||
|
}],
|
||||||
|
afterFileEdit: [{
|
||||||
|
command: 'node .cursor/hooks/post_execution.js',
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
}, null, 2),
|
||||||
|
}, rootDir => {
|
||||||
|
const result = scanSupplyChainIocs({ rootDir });
|
||||||
|
assert.deepStrictEqual(result.findings, []);
|
||||||
|
});
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (test('still flags the bare execution.js payload after a path separator', () => {
|
||||||
|
withFixture({
|
||||||
|
'.cursor/hooks.json': JSON.stringify({
|
||||||
|
hooks: {
|
||||||
|
sessionStart: [{
|
||||||
|
command: 'node .cursor/hooks/execution.js',
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
}, null, 2),
|
||||||
|
}, rootDir => {
|
||||||
|
const result = scanSupplyChainIocs({ rootDir });
|
||||||
|
assert.ok(result.findings.some(finding => finding.indicator === 'execution.js'));
|
||||||
|
});
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
if (test('rejects user-level VS Code task persistence when home scan is enabled', () => {
|
if (test('rejects user-level VS Code task persistence when home scan is enabled', () => {
|
||||||
withFixture({
|
withFixture({
|
||||||
'home/Library/Application Support/Code/User/tasks.json': JSON.stringify({
|
'home/Library/Application Support/Code/User/tasks.json': JSON.stringify({
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user