mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 12:15:57 +00:00
fix(hardening): handle regex literals after else/do
This commit is contained in:
@@ -66,6 +66,22 @@ const SYNC_CALL_CAPTURE_REGEX = new RegExp(
|
||||
);
|
||||
const LEGACY_MARKER_REGEX =
|
||||
/(?:\blegacy\b|\bshim\b|backward compatibility|backwards compatibility|compatibility layer|deprecated.*re-export|re-export.*compatibility)/i;
|
||||
const REGEX_LITERAL_KEYWORDS = new Set([
|
||||
'return',
|
||||
'throw',
|
||||
'case',
|
||||
'else',
|
||||
'do',
|
||||
'delete',
|
||||
'void',
|
||||
'typeof',
|
||||
'instanceof',
|
||||
'in',
|
||||
'of',
|
||||
'yield',
|
||||
'await',
|
||||
'new',
|
||||
]);
|
||||
|
||||
function toPosixPath(filePath) {
|
||||
return filePath.split(path.sep).join('/');
|
||||
@@ -124,9 +140,11 @@ function summarize(items, limit = 10) {
|
||||
}));
|
||||
}
|
||||
|
||||
function isRegexLiteralStart(previousSignificantChar) {
|
||||
function isRegexLiteralStart(previousSignificantChar, previousIdentifier) {
|
||||
return (
|
||||
previousSignificantChar === '' || '([{:;,=!?+-*%^&|~<>'.includes(previousSignificantChar)
|
||||
previousSignificantChar === '' ||
|
||||
'([{:;,=!?+-*%^&|~<>'.includes(previousSignificantChar) ||
|
||||
REGEX_LITERAL_KEYWORDS.has(previousIdentifier)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -141,6 +159,7 @@ function stripComments(sourceText) {
|
||||
let inRegexLiteral = false;
|
||||
let inRegexCharClass = false;
|
||||
let previousSignificantChar = '';
|
||||
let previousIdentifier = '';
|
||||
|
||||
while (index < sourceText.length) {
|
||||
const current = sourceText[index];
|
||||
@@ -207,6 +226,7 @@ function stripComments(sourceText) {
|
||||
}
|
||||
inRegexLiteral = false;
|
||||
previousSignificantChar = 'r';
|
||||
previousIdentifier = '';
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -224,6 +244,7 @@ function stripComments(sourceText) {
|
||||
if (current === "'") {
|
||||
inSingleQuote = false;
|
||||
previousSignificantChar = 's';
|
||||
previousIdentifier = '';
|
||||
}
|
||||
index += 1;
|
||||
continue;
|
||||
@@ -239,6 +260,7 @@ function stripComments(sourceText) {
|
||||
if (current === '"') {
|
||||
inDoubleQuote = false;
|
||||
previousSignificantChar = 's';
|
||||
previousIdentifier = '';
|
||||
}
|
||||
index += 1;
|
||||
continue;
|
||||
@@ -254,6 +276,7 @@ function stripComments(sourceText) {
|
||||
if (current === '`') {
|
||||
inTemplateLiteral = false;
|
||||
previousSignificantChar = 's';
|
||||
previousIdentifier = '';
|
||||
}
|
||||
index += 1;
|
||||
continue;
|
||||
@@ -273,7 +296,7 @@ function stripComments(sourceText) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (current === '/' && isRegexLiteralStart(previousSignificantChar)) {
|
||||
if (current === '/' && isRegexLiteralStart(previousSignificantChar, previousIdentifier)) {
|
||||
output += ' ';
|
||||
index += 1;
|
||||
inRegexLiteral = true;
|
||||
@@ -281,6 +304,19 @@ function stripComments(sourceText) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (/[A-Za-z_$]/.test(current)) {
|
||||
let tokenEnd = index + 1;
|
||||
while (tokenEnd < sourceText.length && /[A-Za-z0-9_$]/.test(sourceText[tokenEnd])) {
|
||||
tokenEnd += 1;
|
||||
}
|
||||
const token = sourceText.slice(index, tokenEnd);
|
||||
output += token;
|
||||
previousSignificantChar = 'i';
|
||||
previousIdentifier = token;
|
||||
index = tokenEnd;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (current === "'") {
|
||||
inSingleQuote = true;
|
||||
output += ' ';
|
||||
@@ -305,6 +341,7 @@ function stripComments(sourceText) {
|
||||
output += current;
|
||||
if (!/\s/.test(current)) {
|
||||
previousSignificantChar = current;
|
||||
previousIdentifier = '';
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
@@ -477,4 +514,13 @@ function main() {
|
||||
console.log(`[hardening-inventory] wrote ${relMd}`);
|
||||
}
|
||||
|
||||
main();
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
buildReport,
|
||||
collectSyncCallSites,
|
||||
renderMarkdown,
|
||||
stripComments,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { describe, expect, test } from 'bun:test';
|
||||
|
||||
const { collectSyncCallSites } = require('../../../scripts/hardening-inventory.js');
|
||||
|
||||
describe('hardening-inventory sync call scanning', () => {
|
||||
test('ignores sync-call names inside regex literals after else', () => {
|
||||
const source = [
|
||||
'if (enabled) {',
|
||||
' run();',
|
||||
'} else /fs\\.readFileSync\\(/.test("pattern");',
|
||||
].join('\n');
|
||||
|
||||
const result = collectSyncCallSites(source);
|
||||
expect(result.count).toBe(0);
|
||||
});
|
||||
|
||||
test('ignores sync-call names inside regex literals after do', () => {
|
||||
const source = 'do /fs\\.writeFileSync\\(/.test("pattern"); while (false);';
|
||||
const result = collectSyncCallSites(source);
|
||||
|
||||
expect(result.count).toBe(0);
|
||||
});
|
||||
|
||||
test('still counts real sync fs call sites', () => {
|
||||
const source = [
|
||||
'if (enabled) {',
|
||||
' run();',
|
||||
'} else /fs\\.readFileSync\\(/.test("pattern");',
|
||||
'fs.readFileSync("file.txt", "utf8");',
|
||||
].join('\n');
|
||||
|
||||
const result = collectSyncCallSites(source);
|
||||
expect(result.count).toBe(1);
|
||||
expect(result.calls).toEqual(['readFileSync']);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user