circle-ir 3.145.0 → 3.147.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
- package/dist/analysis/passes/language-sources-pass.js +183 -0
- package/dist/analysis/passes/language-sources-pass.js.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.js +49 -3
- package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +45 -0
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/browser/circle-ir.js +173 -1
- package/dist/core/circle-ir-core.cjs +30 -0
- package/dist/core/circle-ir-core.js +30 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"language-sources-pass.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/language-sources-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,cAAc,EAAwB,WAAW,EAAO,MAAM,sBAAsB,CAAC;AAC3H,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAqB9E,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;IA0C/B,CAAC;AAiCF,MAAM,WAAW,qBAAqB;IACpC,iBAAiB,EAAE,WAAW,EAAE,CAAC;IACjC,eAAe,EAAE,SAAS,EAAE,CAAC;IAC7B;;;;OAIG;IACH,oBAAoB,EAAE,cAAc,EAAE,CAAC;IACvC;;;OAGG;IACH,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC;;;OAGG;IACH,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7B;;;OAGG;IACH,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAMD,qBAAa,mBAAoB,YAAW,YAAY,CAAC,qBAAqB,CAAC;IAC7E,QAAQ,CAAC,IAAI,sBAAsB;IACnC,QAAQ,CAAC,QAAQ,EAAG,UAAU,CAAU;IAExC,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,qBAAqB;
|
|
1
|
+
{"version":3,"file":"language-sources-pass.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/language-sources-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,cAAc,EAAwB,WAAW,EAAO,MAAM,sBAAsB,CAAC;AAC3H,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAqB9E,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;IA0C/B,CAAC;AAiCF,MAAM,WAAW,qBAAqB;IACpC,iBAAiB,EAAE,WAAW,EAAE,CAAC;IACjC,eAAe,EAAE,SAAS,EAAE,CAAC;IAC7B;;;;OAIG;IACH,oBAAoB,EAAE,cAAc,EAAE,CAAC;IACvC;;;OAGG;IACH,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC;;;OAGG;IACH,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7B;;;OAGG;IACH,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAMD,qBAAa,mBAAoB,YAAW,YAAY,CAAC,qBAAqB,CAAC;IAC7E,QAAQ,CAAC,IAAI,sBAAsB;IACnC,QAAQ,CAAC,QAAQ,EAAG,UAAU,CAAU;IAExC,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,qBAAqB;CAge7C;AAgkBD,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAkG9E;AAED,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAwC5G;AAED,wBAAgB,iCAAiC,CAC/C,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,KAAK,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAoBjD;AAyKD,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAmBpG;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,GACpB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAmCrB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,GACpB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAuDrB;AA+MD,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CAsKvF;AAynED,wBAAgB,sCAAsC,CACpD,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,WAAW,EAAE,CAcf;AAoLD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CA+HnF;AAMD;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CA2BjF;AAOD;;;;;GAKG;AACH,wBAAgB,mCAAmC,CACjD,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CAgCf;AAED;;;;;;GAMG;AACH,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CA2Bf;AAED;;;;;GAKG;AACH,wBAAgB,iCAAiC,CAC/C,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CA0Bf;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CAoCf;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CAwEf;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CAgDf;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CA6Bf;AASD;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CA+C3E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iCAAiC,CAC/C,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CAsDf;AAED;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CA0Gf;AAED;;;;GAIG;AACH,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CAoDf;AAED;;;;;;;;GAQG;AACH,wBAAgB,sCAAsC,CACpD,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CAiGf;AAED;;;;;;;;GAQG;AACH,wBAAgB,gCAAgC,CAC9C,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CAqDf;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wCAAwC,CACtD,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CA0Df;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,8CAA8C,CAC5D,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CA4Df;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,6CAA6C,CAC3D,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CAmFf;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CAwKf;AAaD;;;;;;;GAOG;AACH,wBAAgB,qCAAqC,CACnD,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CA2Gf;AAED;;;;;;;;GAQG;AACH,wBAAgB,uCAAuC,CACrD,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CAqHf;AAED;;;;;;;GAOG;AACH,wBAAgB,qDAAqD,CACnE,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CAuGf;AAED;;;;;;;;;GASG;AACH,wBAAgB,sCAAsC,CACpD,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CAkKf;AAED;;;;;;;;;GASG;AACH,wBAAgB,iCAAiC,CAC/C,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CAiHf;AAED;;;;;;;GAOG;AACH,wBAAgB,mCAAmC,CACjD,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CA+Ef;AAED;;;;;;GAMG;AACH,wBAAgB,gDAAgD,CAC9D,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CAyEf;AAwCD;;;;;;;;;GASG;AACH,wBAAgB,iCAAiC,CAC/C,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CAmGf;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qCAAqC,CACnD,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CAkHf;AAED;;;;;GAKG;AACH,wBAAgB,oCAAoC,CAClD,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CAoEf;AAED;;;;GAIG;AACH,wBAAgB,qCAAqC,CACnD,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CA8Ff;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CAgGf;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CA2If;AAED;;;;;;;;;GASG;AACH,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CA+Kf;AAED;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CA2Jf;AAED;;;;;;;;;GASG;AACH,wBAAgB,gCAAgC,CAC9C,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CAwFf;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CA2Ef;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CAyEf;AAED;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CAsDf;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mCAAmC,CACjD,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CA6Ef;AAED;;;;;;;GAOG;AACH,wBAAgB,mCAAmC,CACjD,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,WAAW,EAAE,CAiFf"}
|
|
@@ -364,6 +364,11 @@ export class LanguageSourcesPass {
|
|
|
364
364
|
// parameterized SQL placeholder sanitizers.
|
|
365
365
|
additionalSanitizers.push(...findJsArgvFormExecSanitizers(code));
|
|
366
366
|
additionalSanitizers.push(...findJsParameterizedSqlSanitizers(code));
|
|
367
|
+
// #223 REG-144-20: JS path.resolve/join + startsWith(root) guard.
|
|
368
|
+
additionalSanitizers.push(...findJsPathResolveStartsWithGuardSanitizers(code));
|
|
369
|
+
// #223 REG-144-17/18/19: JS/TS command allowlist guard
|
|
370
|
+
// (Set.has / Array.includes / indexOf) before execFile/spawn.
|
|
371
|
+
additionalSanitizers.push(...findJsCommandAllowlistGuardSanitizers(code));
|
|
367
372
|
// Sprint 78 (#190): JS misconfig pattern findings — libxmljs noent:true.
|
|
368
373
|
for (const finding of findJsPatternFindings(code, graph.ir.meta.file)) {
|
|
369
374
|
ctx.addFinding(finding);
|
|
@@ -3311,6 +3316,184 @@ function findJsParameterizedSqlSanitizers(code) {
|
|
|
3311
3316
|
}
|
|
3312
3317
|
return sanitizers;
|
|
3313
3318
|
}
|
|
3319
|
+
/**
|
|
3320
|
+
* JS/TS: `path.resolve(root, name)` (or `path.join`) + `.startsWith(root)`
|
|
3321
|
+
* guard sanitizer (cognium-dev #223 REG-144-20).
|
|
3322
|
+
*
|
|
3323
|
+
* Pattern recognized:
|
|
3324
|
+
*
|
|
3325
|
+
* const full = path.resolve(root, name);
|
|
3326
|
+
* if (!full.startsWith(root)) throw new Error('escape');
|
|
3327
|
+
* return fs.readFileSync(full);
|
|
3328
|
+
*
|
|
3329
|
+
* The `path.resolve(root, name)` canonicalizes `..` traversal (an
|
|
3330
|
+
* absolute-path `name` replaces `root` entirely — that case is caught
|
|
3331
|
+
* by the subsequent `full.startsWith(root)` guard with a terminator).
|
|
3332
|
+
* Both the resolve/join and the matching startsWith guard must be
|
|
3333
|
+
* present to emit the sanitizer. Recall on plain `fs.readFile(name)`
|
|
3334
|
+
* without the canonicalize step is unchanged.
|
|
3335
|
+
*
|
|
3336
|
+
* Emits a `path_traversal` + `external_taint_escape` sanitizer at the
|
|
3337
|
+
* resolve line and at every subsequent line that references the
|
|
3338
|
+
* canonicalized variable.
|
|
3339
|
+
*/
|
|
3340
|
+
function findJsPathResolveStartsWithGuardSanitizers(code) {
|
|
3341
|
+
const sanitizers = [];
|
|
3342
|
+
const lines = code.split('\n');
|
|
3343
|
+
// <var> = path.resolve(<root>, ...) | path.join(<root>, ...) | path.normalize(...)
|
|
3344
|
+
// Also accepts `nodePath` / `Path` aliases (`import Path from 'path'`).
|
|
3345
|
+
const resolveDeclRe = /\b(?:const|let|var)\s+([A-Za-z_]\w*)\s*=\s*(?:(?:node:)?path|nodePath|Path)\s*\.\s*(?:resolve|join)\s*\(\s*([A-Za-z_]\w*(?:\.\w+)*)\s*,/;
|
|
3346
|
+
const startsWithGuardRe = (varName, rootName) => new RegExp(`if\\s*\\(\\s*!\\s*${varName}\\s*\\.\\s*startsWith\\s*\\(\\s*${rootName}\\b`);
|
|
3347
|
+
const terminatorRe = /\b(?:throw|return|res\s*\.\s*status\s*\([^)]*\)\s*\.\s*(?:send|end|json))/;
|
|
3348
|
+
const candidates = [];
|
|
3349
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3350
|
+
const m = resolveDeclRe.exec(lines[i]);
|
|
3351
|
+
if (!m)
|
|
3352
|
+
continue;
|
|
3353
|
+
candidates.push({ line: i + 1, fullVar: m[1], rootVar: m[2] });
|
|
3354
|
+
}
|
|
3355
|
+
if (candidates.length === 0)
|
|
3356
|
+
return sanitizers;
|
|
3357
|
+
for (const c of candidates) {
|
|
3358
|
+
const guardRe = startsWithGuardRe(c.fullVar, c.rootVar);
|
|
3359
|
+
let guardLine = -1;
|
|
3360
|
+
for (let l = c.line; l < Math.min(lines.length, c.line + 6); l++) {
|
|
3361
|
+
if (!guardRe.test(lines[l]))
|
|
3362
|
+
continue;
|
|
3363
|
+
if (terminatorRe.test(lines[l]) ||
|
|
3364
|
+
(l + 1 < lines.length && terminatorRe.test(lines[l + 1]))) {
|
|
3365
|
+
guardLine = l + 1;
|
|
3366
|
+
break;
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
if (guardLine < 0)
|
|
3370
|
+
continue;
|
|
3371
|
+
const varRefRe = new RegExp(`\\b${c.fullVar}\\b`);
|
|
3372
|
+
sanitizers.push({
|
|
3373
|
+
type: 'js_path_resolve_startswith_guard',
|
|
3374
|
+
method: 'startsWith',
|
|
3375
|
+
line: c.line,
|
|
3376
|
+
sanitizes: ['path_traversal', 'external_taint_escape'],
|
|
3377
|
+
});
|
|
3378
|
+
for (let l = c.line; l < lines.length; l++) {
|
|
3379
|
+
if (!varRefRe.test(lines[l]))
|
|
3380
|
+
continue;
|
|
3381
|
+
sanitizers.push({
|
|
3382
|
+
type: 'js_path_resolve_startswith_guard',
|
|
3383
|
+
method: 'startsWith',
|
|
3384
|
+
line: l + 1,
|
|
3385
|
+
sanitizes: ['path_traversal', 'external_taint_escape'],
|
|
3386
|
+
});
|
|
3387
|
+
}
|
|
3388
|
+
}
|
|
3389
|
+
return sanitizers;
|
|
3390
|
+
}
|
|
3391
|
+
/**
|
|
3392
|
+
* JS/TS: bounded-command allowlist guard sanitizer (cognium-dev #223
|
|
3393
|
+
* REG-144-17/18/19).
|
|
3394
|
+
*
|
|
3395
|
+
* Pattern recognized:
|
|
3396
|
+
*
|
|
3397
|
+
* const ALLOWED = ['npm', 'yarn', 'pnpm'];
|
|
3398
|
+
* if (!ALLOWED.includes(tool)) throw new Error('unknown tool');
|
|
3399
|
+
* execFile(tool, args);
|
|
3400
|
+
*
|
|
3401
|
+
* When a tainted variable is guarded by an `if (!ALLOW.includes(v))` /
|
|
3402
|
+
* `if (!ALLOW.has(v))` / `if (ALLOW.indexOf(v) < 0)` check followed by
|
|
3403
|
+
* a terminating `throw` / `return`, subsequent references to that
|
|
3404
|
+
* variable point at a value from the allowlist. The value is one of a
|
|
3405
|
+
* fixed set of literals — exec with that value as the program slot
|
|
3406
|
+
* cannot smuggle shell metacharacters. Emits a `command_injection` +
|
|
3407
|
+
* `external_taint_escape` sanitizer for every downstream reference.
|
|
3408
|
+
*
|
|
3409
|
+
* The allowlist name must look like an allowlist: SHOUTY_CASE
|
|
3410
|
+
* (`ALLOWED`, `TOOLS`, `CMDS`) OR contain a substring like `allowed`,
|
|
3411
|
+
* `accepted`, `whitelist`, `permitted`, `valid`, `approved`, `cmds`,
|
|
3412
|
+
* `commands`, `tools`. Conservative bias: an untyped
|
|
3413
|
+
* `if (!x.includes(v))` on an arbitrary `x` does NOT emit the
|
|
3414
|
+
* sanitizer.
|
|
3415
|
+
*/
|
|
3416
|
+
function findJsCommandAllowlistGuardSanitizers(code) {
|
|
3417
|
+
const sanitizers = [];
|
|
3418
|
+
const lines = code.split('\n');
|
|
3419
|
+
// Two-branch match: SHOUTY_CASE (case-sensitive) OR name containing
|
|
3420
|
+
// a known allowlist token (case-insensitive substring test done in
|
|
3421
|
+
// code, not via /i flag, so SHOUTY_CASE stays strict).
|
|
3422
|
+
const shoutyCase = /^[A-Z][A-Z0-9_]+$/;
|
|
3423
|
+
const allowlistTokens = /(?:allowed|accepted|whitelist|permitted|valid|approved|cmds|commands|tools)/i;
|
|
3424
|
+
const isAllowlistName = (n) => shoutyCase.test(n) || allowlistTokens.test(n);
|
|
3425
|
+
// `if (!ALLOW.has(v))` / `if (!ALLOW.includes(v))`
|
|
3426
|
+
const guardHas = /\bif\s*\(\s*!\s*([A-Za-z_]\w*)\s*\.\s*(?:has|includes)\s*\(\s*([A-Za-z_]\w*)\s*\)\s*\)/;
|
|
3427
|
+
// `if (ALLOW.indexOf(v) < 0)` / `=== -1`
|
|
3428
|
+
const guardIndexOf = /\bif\s*\(\s*([A-Za-z_]\w*)\s*\.\s*indexOf\s*\(\s*([A-Za-z_]\w*)\s*\)\s*(?:<\s*0|===\s*-1|==\s*-1)\s*\)/;
|
|
3429
|
+
const terminator = /\b(?:return|throw|res\s*\.\s*status\s*\([^)]*\)\s*\.\s*(?:send|end|json))/;
|
|
3430
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3431
|
+
const line = lines[i];
|
|
3432
|
+
let m = guardHas.exec(line);
|
|
3433
|
+
let allow = null;
|
|
3434
|
+
let guardedVar = null;
|
|
3435
|
+
if (m) {
|
|
3436
|
+
allow = m[1];
|
|
3437
|
+
guardedVar = m[2];
|
|
3438
|
+
}
|
|
3439
|
+
else {
|
|
3440
|
+
m = guardIndexOf.exec(line);
|
|
3441
|
+
if (m) {
|
|
3442
|
+
allow = m[1];
|
|
3443
|
+
guardedVar = m[2];
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
if (!allow || !guardedVar)
|
|
3447
|
+
continue;
|
|
3448
|
+
if (!isAllowlistName(allow))
|
|
3449
|
+
continue;
|
|
3450
|
+
// Confirm the guard body terminates (throw / return). Same-line
|
|
3451
|
+
// single-statement form is common: `if (!A.includes(v)) throw ...;`
|
|
3452
|
+
let bodyHasTerminator = terminator.test(line);
|
|
3453
|
+
let blockEnd = i;
|
|
3454
|
+
if (!bodyHasTerminator) {
|
|
3455
|
+
// Block-form: scan forward for the closing brace and require a
|
|
3456
|
+
// terminator inside.
|
|
3457
|
+
let braceDepth = 0;
|
|
3458
|
+
let started = false;
|
|
3459
|
+
const maxScan = Math.min(lines.length, i + 26);
|
|
3460
|
+
for (let j = i; j < maxScan; j++) {
|
|
3461
|
+
const ln = lines[j];
|
|
3462
|
+
for (const ch of ln) {
|
|
3463
|
+
if (ch === '{') {
|
|
3464
|
+
braceDepth++;
|
|
3465
|
+
started = true;
|
|
3466
|
+
}
|
|
3467
|
+
else if (ch === '}') {
|
|
3468
|
+
braceDepth--;
|
|
3469
|
+
if (started && braceDepth === 0) {
|
|
3470
|
+
blockEnd = j;
|
|
3471
|
+
break;
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
if (started && j > i && terminator.test(ln))
|
|
3476
|
+
bodyHasTerminator = true;
|
|
3477
|
+
if (started && braceDepth === 0 && blockEnd !== i)
|
|
3478
|
+
break;
|
|
3479
|
+
}
|
|
3480
|
+
if (!started || !bodyHasTerminator)
|
|
3481
|
+
continue;
|
|
3482
|
+
}
|
|
3483
|
+
const varRefRe = new RegExp(`\\b${guardedVar}\\b`);
|
|
3484
|
+
for (let l = blockEnd + 1; l < lines.length; l++) {
|
|
3485
|
+
if (!varRefRe.test(lines[l]))
|
|
3486
|
+
continue;
|
|
3487
|
+
sanitizers.push({
|
|
3488
|
+
type: 'js_command_allowlist_guard',
|
|
3489
|
+
method: 'if',
|
|
3490
|
+
line: l + 1,
|
|
3491
|
+
sanitizes: ['command_injection', 'external_taint_escape'],
|
|
3492
|
+
});
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
3495
|
+
return sanitizers;
|
|
3496
|
+
}
|
|
3314
3497
|
/**
|
|
3315
3498
|
* Java: Path.resolve(...).normalize() + startsWith(ROOT) guard
|
|
3316
3499
|
* sanitizer (cognium-dev #216 Sprint 76 Pattern B).
|