mirror of
https://github.com/tiennm99/ccs.git
synced 2026-08-14 02:22:04 +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 =
|
const LEGACY_MARKER_REGEX =
|
||||||
/(?:\blegacy\b|\bshim\b|backward compatibility|backwards compatibility|compatibility layer|deprecated.*re-export|re-export.*compatibility)/i;
|
/(?:\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) {
|
function toPosixPath(filePath) {
|
||||||
return filePath.split(path.sep).join('/');
|
return filePath.split(path.sep).join('/');
|
||||||
@@ -124,9 +140,11 @@ function summarize(items, limit = 10) {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
function isRegexLiteralStart(previousSignificantChar) {
|
function isRegexLiteralStart(previousSignificantChar, previousIdentifier) {
|
||||||
return (
|
return (
|
||||||
previousSignificantChar === '' || '([{:;,=!?+-*%^&|~<>'.includes(previousSignificantChar)
|
previousSignificantChar === '' ||
|
||||||
|
'([{:;,=!?+-*%^&|~<>'.includes(previousSignificantChar) ||
|
||||||
|
REGEX_LITERAL_KEYWORDS.has(previousIdentifier)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,6 +159,7 @@ function stripComments(sourceText) {
|
|||||||
let inRegexLiteral = false;
|
let inRegexLiteral = false;
|
||||||
let inRegexCharClass = false;
|
let inRegexCharClass = false;
|
||||||
let previousSignificantChar = '';
|
let previousSignificantChar = '';
|
||||||
|
let previousIdentifier = '';
|
||||||
|
|
||||||
while (index < sourceText.length) {
|
while (index < sourceText.length) {
|
||||||
const current = sourceText[index];
|
const current = sourceText[index];
|
||||||
@@ -207,6 +226,7 @@ function stripComments(sourceText) {
|
|||||||
}
|
}
|
||||||
inRegexLiteral = false;
|
inRegexLiteral = false;
|
||||||
previousSignificantChar = 'r';
|
previousSignificantChar = 'r';
|
||||||
|
previousIdentifier = '';
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,6 +244,7 @@ function stripComments(sourceText) {
|
|||||||
if (current === "'") {
|
if (current === "'") {
|
||||||
inSingleQuote = false;
|
inSingleQuote = false;
|
||||||
previousSignificantChar = 's';
|
previousSignificantChar = 's';
|
||||||
|
previousIdentifier = '';
|
||||||
}
|
}
|
||||||
index += 1;
|
index += 1;
|
||||||
continue;
|
continue;
|
||||||
@@ -239,6 +260,7 @@ function stripComments(sourceText) {
|
|||||||
if (current === '"') {
|
if (current === '"') {
|
||||||
inDoubleQuote = false;
|
inDoubleQuote = false;
|
||||||
previousSignificantChar = 's';
|
previousSignificantChar = 's';
|
||||||
|
previousIdentifier = '';
|
||||||
}
|
}
|
||||||
index += 1;
|
index += 1;
|
||||||
continue;
|
continue;
|
||||||
@@ -254,6 +276,7 @@ function stripComments(sourceText) {
|
|||||||
if (current === '`') {
|
if (current === '`') {
|
||||||
inTemplateLiteral = false;
|
inTemplateLiteral = false;
|
||||||
previousSignificantChar = 's';
|
previousSignificantChar = 's';
|
||||||
|
previousIdentifier = '';
|
||||||
}
|
}
|
||||||
index += 1;
|
index += 1;
|
||||||
continue;
|
continue;
|
||||||
@@ -273,7 +296,7 @@ function stripComments(sourceText) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (current === '/' && isRegexLiteralStart(previousSignificantChar)) {
|
if (current === '/' && isRegexLiteralStart(previousSignificantChar, previousIdentifier)) {
|
||||||
output += ' ';
|
output += ' ';
|
||||||
index += 1;
|
index += 1;
|
||||||
inRegexLiteral = true;
|
inRegexLiteral = true;
|
||||||
@@ -281,6 +304,19 @@ function stripComments(sourceText) {
|
|||||||
continue;
|
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 === "'") {
|
if (current === "'") {
|
||||||
inSingleQuote = true;
|
inSingleQuote = true;
|
||||||
output += ' ';
|
output += ' ';
|
||||||
@@ -305,6 +341,7 @@ function stripComments(sourceText) {
|
|||||||
output += current;
|
output += current;
|
||||||
if (!/\s/.test(current)) {
|
if (!/\s/.test(current)) {
|
||||||
previousSignificantChar = current;
|
previousSignificantChar = current;
|
||||||
|
previousIdentifier = '';
|
||||||
}
|
}
|
||||||
index += 1;
|
index += 1;
|
||||||
}
|
}
|
||||||
@@ -477,4 +514,13 @@ function main() {
|
|||||||
console.log(`[hardening-inventory] wrote ${relMd}`);
|
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