circle-ir 4.9.27 → 4.9.28
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/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +18 -3
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/findings.d.ts.map +1 -1
- package/dist/analysis/findings.js +130 -0
- package/dist/analysis/findings.js.map +1 -1
- package/dist/analysis/interprocedural.d.ts.map +1 -1
- package/dist/analysis/interprocedural.js +63 -0
- package/dist/analysis/interprocedural.js.map +1 -1
- package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
- package/dist/analysis/passes/language-sources-pass.js +108 -1
- package/dist/analysis/passes/language-sources-pass.js.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.d.ts.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.js +97 -4
- package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
- package/dist/analysis/passes/variable-shadowing-pass.d.ts.map +1 -1
- package/dist/analysis/passes/variable-shadowing-pass.js +15 -0
- package/dist/analysis/passes/variable-shadowing-pass.js.map +1 -1
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +21 -1
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/browser/circle-ir.js +322 -10
- package/dist/core/circle-ir-core.cjs +89 -5
- package/dist/core/circle-ir-core.js +89 -5
- package/dist/core/extractors/calls.js +127 -1
- package/dist/core/extractors/calls.js.map +1 -1
- package/package.json +1 -1
|
@@ -506,7 +506,7 @@ export class LanguageSourcesPass {
|
|
|
506
506
|
// guard (the canonicalize-then-contain path-traversal defence). Java and
|
|
507
507
|
// JS/TS already credit their equivalents above; C# was the gap.
|
|
508
508
|
if (language === 'csharp') {
|
|
509
|
-
additionalSanitizers.push(...findCSharpFullPathStartsWithGuardSanitizers(code));
|
|
509
|
+
additionalSanitizers.push(...findCSharpFullPathStartsWithGuardSanitizers(code), ...findCSharpEarlyReturnGuardSanitizers(code, language));
|
|
510
510
|
}
|
|
511
511
|
// -- Java: Sprint 73 (#216 Pattern A) — Jackson readValue / Gson
|
|
512
512
|
// fromJson recognized as ETE terminator (does not affect
|
|
@@ -5641,6 +5641,113 @@ function guardRejects(lines, i, terminatorRe, window) {
|
|
|
5641
5641
|
* form `if (x.StartsWith(root)) { … }` still reports, and a test pins that so
|
|
5642
5642
|
* the gap stays visible rather than silently assumed.
|
|
5643
5643
|
*/
|
|
5644
|
+
/**
|
|
5645
|
+
* cognium-dev#272 — a validating predicate followed by an EARLY RETURN is a
|
|
5646
|
+
* guard, and the value reaching the sink afterwards is already validated.
|
|
5647
|
+
*
|
|
5648
|
+
* if (Array.IndexOf(Allowed, u.Host) < 0) return; // host allow-list
|
|
5649
|
+
* await client.GetStringAsync(input); // reported ssrf
|
|
5650
|
+
*
|
|
5651
|
+
* if (!input.All(char.IsLetterOrDigit)) return; // char-class check
|
|
5652
|
+
* doc.SelectSingleNode("/a[@b='" + input + "']"); // reported xpath
|
|
5653
|
+
*
|
|
5654
|
+
* Both safe mirrors are clean of their *family* finding after #272's earlier
|
|
5655
|
+
* fixes, and both still emit a finding because the guard itself is not
|
|
5656
|
+
* credited. The shape is shared with #286-B and with the Java `Set.contains`
|
|
5657
|
+
* guard in #293, so the recogniser is written around the PREDICATE rather than
|
|
5658
|
+
* around any one sink family.
|
|
5659
|
+
*
|
|
5660
|
+
* Deliberately limited to predicates that are self-evidently validating:
|
|
5661
|
+
* allow-list membership and character-class validation. A user-defined
|
|
5662
|
+
* boolean helper (`if (!IsAllowed(input)) return;`, #286-B) is NOT credited
|
|
5663
|
+
* here — crediting an arbitrary predicate by name would silence a real flow
|
|
5664
|
+
* whenever the helper does not actually validate, which is a false negative no
|
|
5665
|
+
* test would catch. That half needs its own evidence.
|
|
5666
|
+
*
|
|
5667
|
+
* Only the REJECTING direction counts, via `guardRejects`: a guard whose body
|
|
5668
|
+
* falls through has not stopped anything.
|
|
5669
|
+
*/
|
|
5670
|
+
function findCSharpEarlyReturnGuardSanitizers(code, language) {
|
|
5671
|
+
if (language !== 'csharp')
|
|
5672
|
+
return [];
|
|
5673
|
+
const sanitizers = [];
|
|
5674
|
+
const lines = code.split('\n');
|
|
5675
|
+
const terminatorRe = /\b(throw|return)\b/;
|
|
5676
|
+
// Allow-list membership, rejecting form:
|
|
5677
|
+
// if (Array.IndexOf(Allowed, x) < 0) return;
|
|
5678
|
+
// if (!Allowed.Contains(x)) return;
|
|
5679
|
+
const allowlistRes = [
|
|
5680
|
+
/\bif\s*\(\s*Array\s*\.\s*IndexOf\s*\([^,]+,\s*([A-Za-z_][\w.]*)\s*\)\s*<\s*0\s*\)/,
|
|
5681
|
+
/\bif\s*\(\s*!\s*[A-Za-z_][\w.]*\s*\.\s*Contains\s*\(\s*([A-Za-z_][\w.]*)\s*\)\s*\)/,
|
|
5682
|
+
];
|
|
5683
|
+
// Character-class validation, rejecting form:
|
|
5684
|
+
// if (!input.All(char.IsLetterOrDigit)) return;
|
|
5685
|
+
const charClassRe = /\bif\s*\(\s*!\s*([A-Za-z_][\w.]*)\s*\.\s*All\s*\(\s*char\s*\.\s*Is[A-Za-z]+\s*\)\s*\)/;
|
|
5686
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5687
|
+
let guardedExpr = null;
|
|
5688
|
+
for (const re of allowlistRes) {
|
|
5689
|
+
const m = re.exec(lines[i]);
|
|
5690
|
+
if (m) {
|
|
5691
|
+
guardedExpr = m[1];
|
|
5692
|
+
break;
|
|
5693
|
+
}
|
|
5694
|
+
}
|
|
5695
|
+
if (!guardedExpr) {
|
|
5696
|
+
const m = charClassRe.exec(lines[i]);
|
|
5697
|
+
if (m)
|
|
5698
|
+
guardedExpr = m[1];
|
|
5699
|
+
}
|
|
5700
|
+
if (!guardedExpr)
|
|
5701
|
+
continue;
|
|
5702
|
+
if (!guardRejects(lines, i, terminatorRe, 6))
|
|
5703
|
+
continue;
|
|
5704
|
+
// The guarded expression may be a derivation of the tainted value
|
|
5705
|
+
// (`u.Host` guards `u`, and `u` came from `input`), so credit the root
|
|
5706
|
+
// identifier as well as the full expression.
|
|
5707
|
+
const root = guardedExpr.split('.')[0];
|
|
5708
|
+
const guarded = new Set([guardedExpr, root]);
|
|
5709
|
+
const assignRe = /^\s*(?:(?:var|string)\s+)?([A-Za-z_]\w*)\s*=(?!=)\s*(.*)$/;
|
|
5710
|
+
// Walk BACKWARD first: the guard often validates a DERIVATION of the
|
|
5711
|
+
// tainted value while the sink uses the original.
|
|
5712
|
+
//
|
|
5713
|
+
// var u = new Uri(input);
|
|
5714
|
+
// if (Array.IndexOf(Allowed, u.Host) < 0) return; // guards u.Host
|
|
5715
|
+
// await client.GetStringAsync(input); // uses input
|
|
5716
|
+
//
|
|
5717
|
+
// Allow-listing the host of the URI built from `input` constrains where
|
|
5718
|
+
// `input` can point, so the original is guarded too. Bounded to the
|
|
5719
|
+
// identifiers that actually fed the guarded root, transitively.
|
|
5720
|
+
for (let b = i - 1; b >= 0; b--) {
|
|
5721
|
+
const a = assignRe.exec(lines[b]);
|
|
5722
|
+
if (!a || !guarded.has(a[1]))
|
|
5723
|
+
continue;
|
|
5724
|
+
for (const id of a[2].match(/[A-Za-z_]\w*/g) ?? [])
|
|
5725
|
+
guarded.add(id);
|
|
5726
|
+
}
|
|
5727
|
+
// Anything assigned FROM a guarded value downstream is guarded too.
|
|
5728
|
+
const mentions = (t) => {
|
|
5729
|
+
for (const n of guarded)
|
|
5730
|
+
if (new RegExp(`\\b${n.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(t))
|
|
5731
|
+
return true;
|
|
5732
|
+
return false;
|
|
5733
|
+
};
|
|
5734
|
+
for (let l = i + 1; l < lines.length; l++) {
|
|
5735
|
+
const a = assignRe.exec(lines[l]);
|
|
5736
|
+
if (a && mentions(a[2]))
|
|
5737
|
+
guarded.add(a[1]);
|
|
5738
|
+
if (mentions(lines[l])) {
|
|
5739
|
+
sanitizers.push({
|
|
5740
|
+
type: 'csharp_early_return_guard',
|
|
5741
|
+
method: 'guard',
|
|
5742
|
+
line: l + 1,
|
|
5743
|
+
sanitizes: ['ssrf', 'xpath_injection', 'ldap_injection', 'path_traversal',
|
|
5744
|
+
'command_injection', 'sql_injection', 'external_taint_escape'],
|
|
5745
|
+
});
|
|
5746
|
+
}
|
|
5747
|
+
}
|
|
5748
|
+
}
|
|
5749
|
+
return sanitizers;
|
|
5750
|
+
}
|
|
5644
5751
|
function findCSharpFullPathStartsWithGuardSanitizers(code) {
|
|
5645
5752
|
const sanitizers = [];
|
|
5646
5753
|
const lines = code.split('\n');
|