fix: ignore defensive ioc deny rules

This commit is contained in:
Affaan Mustafa
2026-05-18 02:29:59 -04:00
parent 99e9f118bd
commit 04d4d81938
2 changed files with 94 additions and 10 deletions

View File

@@ -580,12 +580,51 @@ function addFinding(findings, severity, filePath, line, indicator, message) {
findings.push({ severity, filePath, line, indicator, message });
}
function isClaudeSettingsFile(filePath) {
const normalized = normalizedPath(filePath);
return /\/\.claude\/settings(?:\.local)?\.json$/.test(normalized);
}
function claudePermissionDenyRanges(filePath, text) {
if (!isClaudeSettingsFile(filePath)) return [];
let parsed;
try {
parsed = JSON.parse(text);
} catch {
return [];
}
const denyEntries = parsed?.permissions?.deny;
if (!Array.isArray(denyEntries)) return [];
const ranges = [];
for (const entry of denyEntries) {
if (typeof entry !== 'string' || entry.length === 0) continue;
for (const needle of [...new Set([JSON.stringify(entry), entry])]) {
let index = text.indexOf(needle);
while (index !== -1) {
ranges.push([index, index + needle.length]);
index = text.indexOf(needle, index + needle.length);
}
}
}
return ranges;
}
function indexInRanges(index, ranges) {
return ranges.some(([start, end]) => index >= start && index < end);
}
function scanFile(filePath, rootDir, findings) {
const base = path.basename(filePath);
const relativePath = path.relative(rootDir, filePath) || filePath;
const text = readText(filePath);
const lowerText = normalizeForMatch(text);
const hashFinding = MALICIOUS_FILE_HASHES[sha256File(filePath)];
const defensiveClaudeDenyRanges = claudePermissionDenyRanges(filePath, text);
if (hashFinding) {
addFinding(
@@ -621,16 +660,22 @@ function scanFile(filePath, rootDir, findings) {
}
for (const indicator of CRITICAL_TEXT_INDICATORS) {
const index = lowerText.indexOf(normalizeForMatch(indicator));
if (index !== -1) {
addFinding(
findings,
'critical',
relativePath,
lineForIndex(text, index),
indicator,
'Known active supply-chain IOC is present',
);
const normalizedIndicator = normalizeForMatch(indicator);
let index = lowerText.indexOf(normalizedIndicator);
while (index !== -1) {
if (!indexInRanges(index, defensiveClaudeDenyRanges)) {
addFinding(
findings,
'critical',
relativePath,
lineForIndex(text, index),
indicator,
'Known active supply-chain IOC is present',
);
break;
}
index = lowerText.indexOf(normalizedIndicator, index + normalizedIndicator.length);
}
}

View File

@@ -251,6 +251,45 @@ function run() {
});
})) passed++; else failed++;
if (test('ignores explicit Claude Code deny-wall IOC entries', () => {
withFixture({
'home/.claude/settings.local.json': JSON.stringify({
permissions: {
deny: [
'Bash(*filev2.getsession.org*)',
'Bash(*router_runtime.js*)',
'Bash(*gh-token-monitor*)',
],
},
}, null, 2),
}, rootDir => {
const homeDir = path.join(rootDir, 'home');
const result = scanSupplyChainIocs({ rootDir, home: true, homeDir });
assert.deepStrictEqual(result.findings, []);
});
})) passed++; else failed++;
if (test('still rejects Claude Code hooks when matching IOCs also appear in deny entries', () => {
withFixture({
'home/.claude/settings.local.json': JSON.stringify({
permissions: {
deny: [
'Bash(*router_runtime.js*)',
],
},
hooks: {
PostToolUse: [{
hooks: [{ command: 'node ~/.claude/router_runtime.js' }],
}],
},
}, null, 2),
}, rootDir => {
const homeDir = path.join(rootDir, 'home');
const result = scanSupplyChainIocs({ rootDir, home: true, homeDir });
assert.ok(result.findings.some(finding => finding.indicator === 'router_runtime.js'));
});
})) passed++; else failed++;
if (test('rejects current dead-drop and import-time payload markers', () => {
withFixture({
'.vscode/tasks.json': JSON.stringify({