@clear-capabilities/agentic-security-scanner 0.78.0 → 0.79.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.
Files changed (76) hide show
  1. package/bin/.agentic-security/findings.json +16 -16
  2. package/bin/.agentic-security/last-scan.json +16 -16
  3. package/bin/.agentic-security/last-scan.json.sig +1 -1
  4. package/bin/.agentic-security/scan-history.json +51 -0
  5. package/bin/.agentic-security/streak.json +5 -5
  6. package/bin/agentic-security.js +22 -7
  7. package/dist/178.index.js +1 -1
  8. package/dist/384.index.js +1 -1
  9. package/dist/476.index.js +5 -5
  10. package/dist/637.index.js +1 -1
  11. package/dist/700.index.js +138 -0
  12. package/dist/718.index.js +53 -0
  13. package/dist/838.index.js +1 -1
  14. package/dist/985.index.js +5 -0
  15. package/dist/agentic-security.mjs +1 -1
  16. package/dist/agentic-security.mjs.sha256 +1 -1
  17. package/package.json +2 -2
  18. package/src/dataflow/engine.js +52 -8
  19. package/src/engine.js +107 -6
  20. package/src/integrations/index.js +2 -1
  21. package/src/ir/callgraph.js +27 -7
  22. package/src/llm-validator/index.js +7 -5
  23. package/src/mcp/audit.js +5 -0
  24. package/src/posture/calibration-drift.js +2 -1
  25. package/src/posture/calibration.js +3 -2
  26. package/src/posture/fix-history.js +8 -2
  27. package/src/posture/profile.js +4 -5
  28. package/src/posture/rule-overrides.js +2 -3
  29. package/src/posture/rule-pack-signing.js +2 -3
  30. package/src/posture/rule-synthesis.js +5 -6
  31. package/src/posture/security-trend.js +4 -7
  32. package/src/posture/state-dir.js +124 -0
  33. package/src/posture/streak.js +3 -0
  34. package/src/posture/suppressions.js +5 -8
  35. package/src/posture/triage.js +3 -5
  36. package/src/posture/validator-metrics.js +3 -6
  37. package/src/sast/db-taint.js +24 -0
  38. package/src/sast/rust.js +26 -0
  39. package/src/sca/binary-metadata.js +124 -0
  40. package/src/sca/py-package-functions.js +118 -0
  41. package/src/sca/vendor-detect.js +53 -0
  42. package/src/.agentic-security/findings.json +0 -82642
  43. package/src/.agentic-security/last-scan.json +0 -82642
  44. package/src/.agentic-security/last-scan.json.sig +0 -1
  45. package/src/.agentic-security/scan-history.json +0 -10054
  46. package/src/.agentic-security/streak.json +0 -21
  47. package/src/dataflow/.agentic-security/findings.json +0 -3515
  48. package/src/dataflow/.agentic-security/last-scan.json +0 -3515
  49. package/src/dataflow/.agentic-security/last-scan.json.sig +0 -1
  50. package/src/dataflow/.agentic-security/scan-history.json +0 -702
  51. package/src/dataflow/.agentic-security/streak.json +0 -22
  52. package/src/ir/.agentic-security/findings.json +0 -3777
  53. package/src/ir/.agentic-security/last-scan.json +0 -3777
  54. package/src/ir/.agentic-security/last-scan.json.sig +0 -1
  55. package/src/ir/.agentic-security/scan-history.json +0 -771
  56. package/src/ir/.agentic-security/streak.json +0 -21
  57. package/src/posture/.agentic-security/findings.json +0 -51562
  58. package/src/posture/.agentic-security/last-scan.json +0 -51562
  59. package/src/posture/.agentic-security/last-scan.json.sig +0 -1
  60. package/src/posture/.agentic-security/scan-history.json +0 -650
  61. package/src/posture/.agentic-security/streak.json +0 -20
  62. package/src/report/.agentic-security/findings.json +0 -80
  63. package/src/report/.agentic-security/last-scan.json +0 -80
  64. package/src/report/.agentic-security/last-scan.json.sig +0 -1
  65. package/src/report/.agentic-security/scan-history.json +0 -35
  66. package/src/report/.agentic-security/streak.json +0 -22
  67. package/src/sast/.agentic-security/findings.json +0 -5190
  68. package/src/sast/.agentic-security/last-scan.json +0 -5190
  69. package/src/sast/.agentic-security/last-scan.json.sig +0 -1
  70. package/src/sast/.agentic-security/scan-history.json +0 -408
  71. package/src/sast/.agentic-security/streak.json +0 -20
  72. package/src/sca/.agentic-security/findings.json +0 -1587
  73. package/src/sca/.agentic-security/last-scan.json +0 -1587
  74. package/src/sca/.agentic-security/last-scan.json.sig +0 -1
  75. package/src/sca/.agentic-security/scan-history.json +0 -36
  76. package/src/sca/.agentic-security/streak.json +0 -21
@@ -1,7 +1,7 @@
1
1
  {
2
- "scanId": "0c2c7713-e7de-4bc9-ab48-f6473ad81d9f",
3
- "startedAt": "2026-05-27T11:23:10.965Z",
4
- "durationMs": 278,
2
+ "scanId": "bb8f9491-46eb-4ec6-a21b-df7b0a436001",
3
+ "startedAt": "2026-05-28T14:16:52.841Z",
4
+ "durationMs": 320,
5
5
  "scanned": {
6
6
  "files": 7,
7
7
  "lines": 0
@@ -759,7 +759,7 @@
759
759
  "attackPlaybook": null
760
760
  },
761
761
  {
762
- "id": "toctou-fs:agentic-security.js:362",
762
+ "id": "toctou-fs:agentic-security.js:367",
763
763
  "kind": "sast",
764
764
  "severity": "medium",
765
765
  "vuln": "TOCTOU: file existence/permission check before open",
@@ -767,7 +767,7 @@
767
767
  "owaspLlm": null,
768
768
  "stride": "Tampering",
769
769
  "file": "agentic-security.js",
770
- "line": 362,
770
+ "line": 367,
771
771
  "snippet": "if (args.flags['since-baseline'] && fs.existsSync(baselinePath)) {",
772
772
  "fix": null,
773
773
  "reachable": false,
@@ -848,7 +848,7 @@
848
848
  "dominantDriver": "legal counsel",
849
849
  "comparable": "Generic finding — likely cost driven by user count + jurisdiction stack",
850
850
  "confidence": "low",
851
- "narrative": "TOCTOU: file existence/permission check before open on `agentic-security.js:362` could expose configuration / internal data. Context: general SaaS / no specific regulatory exposure. Estimated cost: best $23k · likely $136k · worst $775k. Dominant driver: legal counsel. Comparable: Generic finding — likely cost driven by user count + jurisdiction stack."
851
+ "narrative": "TOCTOU: file existence/permission check before open on `agentic-security.js:367` could expose configuration / internal data. Context: general SaaS / no specific regulatory exposure. Estimated cost: best $23k · likely $136k · worst $775k. Dominant driver: legal counsel. Comparable: Generic finding — likely cost driven by user count + jurisdiction stack."
852
852
  },
853
853
  "stableId": "ba3080b44d262d10",
854
854
  "confidenceTier": "medium",
@@ -981,7 +981,7 @@
981
981
  "attackPlaybook": null
982
982
  },
983
983
  {
984
- "id": "toctou-fs:agentic-security.js:1136",
984
+ "id": "toctou-fs:agentic-security.js:1151",
985
985
  "kind": "sast",
986
986
  "severity": "medium",
987
987
  "vuln": "TOCTOU: file existence/permission check before open",
@@ -989,7 +989,7 @@
989
989
  "owaspLlm": null,
990
990
  "stride": "Tampering",
991
991
  "file": "agentic-security.js",
992
- "line": 1136,
992
+ "line": 1151,
993
993
  "snippet": "const st = fs.statSync(abs);",
994
994
  "fix": null,
995
995
  "reachable": false,
@@ -1070,7 +1070,7 @@
1070
1070
  "dominantDriver": "legal counsel",
1071
1071
  "comparable": "Generic finding — likely cost driven by user count + jurisdiction stack",
1072
1072
  "confidence": "low",
1073
- "narrative": "TOCTOU: file existence/permission check before open on `agentic-security.js:1136` could expose configuration / internal data. Context: general SaaS / no specific regulatory exposure. Estimated cost: best $23k · likely $136k · worst $775k. Dominant driver: legal counsel. Comparable: Generic finding — likely cost driven by user count + jurisdiction stack."
1073
+ "narrative": "TOCTOU: file existence/permission check before open on `agentic-security.js:1151` could expose configuration / internal data. Context: general SaaS / no specific regulatory exposure. Estimated cost: best $23k · likely $136k · worst $775k. Dominant driver: legal counsel. Comparable: Generic finding — likely cost driven by user count + jurisdiction stack."
1074
1074
  },
1075
1075
  "stableId": "17f63a600e3a68b4",
1076
1076
  "confidenceTier": "medium",
@@ -1701,14 +1701,14 @@
1701
1701
  "family": null
1702
1702
  },
1703
1703
  {
1704
- "id": "logic:agentic-security.js:362:TOCTOU:_existsSync_followed_by_file_op",
1704
+ "id": "logic:agentic-security.js:367:TOCTOU:_existsSync_followed_by_file_op",
1705
1705
  "kind": "logic",
1706
1706
  "severity": "medium",
1707
1707
  "vuln": "TOCTOU: existsSync followed by file op",
1708
1708
  "cwe": "CWE-367",
1709
1709
  "stride": "Tampering",
1710
1710
  "file": "agentic-security.js",
1711
- "line": 362,
1711
+ "line": 367,
1712
1712
  "snippet": "if (args.flags['since-baseline'] && fs.existsSync(baselinePath)) {",
1713
1713
  "fix": {
1714
1714
  "description": "Replace the check-then-act sequence with a single atomic operation (e.g., `fs.open` with appropriate flags). Between `existsSync` and the file op the file can be replaced by a symlink or removed.",
@@ -1778,7 +1778,7 @@
1778
1778
  "dominantDriver": "legal counsel",
1779
1779
  "comparable": "Generic finding — likely cost driven by user count + jurisdiction stack",
1780
1780
  "confidence": "low",
1781
- "narrative": "TOCTOU: existsSync followed by file op on `agentic-security.js:362` could expose configuration / internal data. Context: general SaaS / no specific regulatory exposure. Estimated cost: best $23k · likely $136k · worst $775k. Dominant driver: legal counsel. Comparable: Generic finding — likely cost driven by user count + jurisdiction stack."
1781
+ "narrative": "TOCTOU: existsSync followed by file op on `agentic-security.js:367` could expose configuration / internal data. Context: general SaaS / no specific regulatory exposure. Estimated cost: best $23k · likely $136k · worst $775k. Dominant driver: legal counsel. Comparable: Generic finding — likely cost driven by user count + jurisdiction stack."
1782
1782
  },
1783
1783
  "parser": "LOGIC",
1784
1784
  "family": null
@@ -1787,7 +1787,7 @@
1787
1787
  "bundles": [],
1788
1788
  "routes": [],
1789
1789
  "components": [],
1790
- "suppressedCount": 39,
1790
+ "suppressedCount": 42,
1791
1791
  "blastRadiusSignals": {
1792
1792
  "industry": "generic",
1793
1793
  "industryConfidence": "low",
@@ -1805,7 +1805,7 @@
1805
1805
  "_v3": {
1806
1806
  "counterfactual": {
1807
1807
  "spofControls": [],
1808
- "controlsDetected": 118
1808
+ "controlsDetected": 119
1809
1809
  },
1810
1810
  "threatModel": {
1811
1811
  "summary": {
@@ -1854,13 +1854,13 @@
1854
1854
  {
1855
1855
  "vuln": "TOCTOU: file existence/permission check before open",
1856
1856
  "file": "agentic-security.js",
1857
- "line": 362,
1857
+ "line": 367,
1858
1858
  "severity": "medium"
1859
1859
  },
1860
1860
  {
1861
1861
  "vuln": "TOCTOU: file existence/permission check before open",
1862
1862
  "file": "agentic-security.js",
1863
- "line": 1136,
1863
+ "line": 1151,
1864
1864
  "severity": "medium"
1865
1865
  }
1866
1866
  ],
@@ -1,7 +1,7 @@
1
1
  {
2
- "scanId": "0c2c7713-e7de-4bc9-ab48-f6473ad81d9f",
3
- "startedAt": "2026-05-27T11:23:10.965Z",
4
- "durationMs": 278,
2
+ "scanId": "bb8f9491-46eb-4ec6-a21b-df7b0a436001",
3
+ "startedAt": "2026-05-28T14:16:52.841Z",
4
+ "durationMs": 320,
5
5
  "scanned": {
6
6
  "files": 7,
7
7
  "lines": 0
@@ -759,7 +759,7 @@
759
759
  "attackPlaybook": null
760
760
  },
761
761
  {
762
- "id": "toctou-fs:agentic-security.js:362",
762
+ "id": "toctou-fs:agentic-security.js:367",
763
763
  "kind": "sast",
764
764
  "severity": "medium",
765
765
  "vuln": "TOCTOU: file existence/permission check before open",
@@ -767,7 +767,7 @@
767
767
  "owaspLlm": null,
768
768
  "stride": "Tampering",
769
769
  "file": "agentic-security.js",
770
- "line": 362,
770
+ "line": 367,
771
771
  "snippet": "if (args.flags['since-baseline'] && fs.existsSync(baselinePath)) {",
772
772
  "fix": null,
773
773
  "reachable": false,
@@ -848,7 +848,7 @@
848
848
  "dominantDriver": "legal counsel",
849
849
  "comparable": "Generic finding — likely cost driven by user count + jurisdiction stack",
850
850
  "confidence": "low",
851
- "narrative": "TOCTOU: file existence/permission check before open on `agentic-security.js:362` could expose configuration / internal data. Context: general SaaS / no specific regulatory exposure. Estimated cost: best $23k · likely $136k · worst $775k. Dominant driver: legal counsel. Comparable: Generic finding — likely cost driven by user count + jurisdiction stack."
851
+ "narrative": "TOCTOU: file existence/permission check before open on `agentic-security.js:367` could expose configuration / internal data. Context: general SaaS / no specific regulatory exposure. Estimated cost: best $23k · likely $136k · worst $775k. Dominant driver: legal counsel. Comparable: Generic finding — likely cost driven by user count + jurisdiction stack."
852
852
  },
853
853
  "stableId": "ba3080b44d262d10",
854
854
  "confidenceTier": "medium",
@@ -981,7 +981,7 @@
981
981
  "attackPlaybook": null
982
982
  },
983
983
  {
984
- "id": "toctou-fs:agentic-security.js:1136",
984
+ "id": "toctou-fs:agentic-security.js:1151",
985
985
  "kind": "sast",
986
986
  "severity": "medium",
987
987
  "vuln": "TOCTOU: file existence/permission check before open",
@@ -989,7 +989,7 @@
989
989
  "owaspLlm": null,
990
990
  "stride": "Tampering",
991
991
  "file": "agentic-security.js",
992
- "line": 1136,
992
+ "line": 1151,
993
993
  "snippet": "const st = fs.statSync(abs);",
994
994
  "fix": null,
995
995
  "reachable": false,
@@ -1070,7 +1070,7 @@
1070
1070
  "dominantDriver": "legal counsel",
1071
1071
  "comparable": "Generic finding — likely cost driven by user count + jurisdiction stack",
1072
1072
  "confidence": "low",
1073
- "narrative": "TOCTOU: file existence/permission check before open on `agentic-security.js:1136` could expose configuration / internal data. Context: general SaaS / no specific regulatory exposure. Estimated cost: best $23k · likely $136k · worst $775k. Dominant driver: legal counsel. Comparable: Generic finding — likely cost driven by user count + jurisdiction stack."
1073
+ "narrative": "TOCTOU: file existence/permission check before open on `agentic-security.js:1151` could expose configuration / internal data. Context: general SaaS / no specific regulatory exposure. Estimated cost: best $23k · likely $136k · worst $775k. Dominant driver: legal counsel. Comparable: Generic finding — likely cost driven by user count + jurisdiction stack."
1074
1074
  },
1075
1075
  "stableId": "17f63a600e3a68b4",
1076
1076
  "confidenceTier": "medium",
@@ -1701,14 +1701,14 @@
1701
1701
  "family": null
1702
1702
  },
1703
1703
  {
1704
- "id": "logic:agentic-security.js:362:TOCTOU:_existsSync_followed_by_file_op",
1704
+ "id": "logic:agentic-security.js:367:TOCTOU:_existsSync_followed_by_file_op",
1705
1705
  "kind": "logic",
1706
1706
  "severity": "medium",
1707
1707
  "vuln": "TOCTOU: existsSync followed by file op",
1708
1708
  "cwe": "CWE-367",
1709
1709
  "stride": "Tampering",
1710
1710
  "file": "agentic-security.js",
1711
- "line": 362,
1711
+ "line": 367,
1712
1712
  "snippet": "if (args.flags['since-baseline'] && fs.existsSync(baselinePath)) {",
1713
1713
  "fix": {
1714
1714
  "description": "Replace the check-then-act sequence with a single atomic operation (e.g., `fs.open` with appropriate flags). Between `existsSync` and the file op the file can be replaced by a symlink or removed.",
@@ -1778,7 +1778,7 @@
1778
1778
  "dominantDriver": "legal counsel",
1779
1779
  "comparable": "Generic finding — likely cost driven by user count + jurisdiction stack",
1780
1780
  "confidence": "low",
1781
- "narrative": "TOCTOU: existsSync followed by file op on `agentic-security.js:362` could expose configuration / internal data. Context: general SaaS / no specific regulatory exposure. Estimated cost: best $23k · likely $136k · worst $775k. Dominant driver: legal counsel. Comparable: Generic finding — likely cost driven by user count + jurisdiction stack."
1781
+ "narrative": "TOCTOU: existsSync followed by file op on `agentic-security.js:367` could expose configuration / internal data. Context: general SaaS / no specific regulatory exposure. Estimated cost: best $23k · likely $136k · worst $775k. Dominant driver: legal counsel. Comparable: Generic finding — likely cost driven by user count + jurisdiction stack."
1782
1782
  },
1783
1783
  "parser": "LOGIC",
1784
1784
  "family": null
@@ -1787,7 +1787,7 @@
1787
1787
  "bundles": [],
1788
1788
  "routes": [],
1789
1789
  "components": [],
1790
- "suppressedCount": 39,
1790
+ "suppressedCount": 42,
1791
1791
  "blastRadiusSignals": {
1792
1792
  "industry": "generic",
1793
1793
  "industryConfidence": "low",
@@ -1805,7 +1805,7 @@
1805
1805
  "_v3": {
1806
1806
  "counterfactual": {
1807
1807
  "spofControls": [],
1808
- "controlsDetected": 118
1808
+ "controlsDetected": 119
1809
1809
  },
1810
1810
  "threatModel": {
1811
1811
  "summary": {
@@ -1854,13 +1854,13 @@
1854
1854
  {
1855
1855
  "vuln": "TOCTOU: file existence/permission check before open",
1856
1856
  "file": "agentic-security.js",
1857
- "line": 362,
1857
+ "line": 367,
1858
1858
  "severity": "medium"
1859
1859
  },
1860
1860
  {
1861
1861
  "vuln": "TOCTOU: file existence/permission check before open",
1862
1862
  "file": "agentic-security.js",
1863
- "line": 1136,
1863
+ "line": 1151,
1864
1864
  "severity": "medium"
1865
1865
  }
1866
1866
  ],
@@ -1 +1 @@
1
- 1a7a7e91a1b09ea956d4e722c3d9df15e8c662dba64e487bdc6f8ba875fd48b9
1
+ 5bb921912d508574fede241a7e16cd7ac261f415b200e9eff04093fa70426dd1
@@ -111,5 +111,56 @@
111
111
  "toctou-fs:agentic-security.js:1136",
112
112
  "toctou-fs:agentic-security.js:362"
113
113
  ]
114
+ },
115
+ {
116
+ "timestamp": "2026-05-28T14:13:54.951Z",
117
+ "label": "scan",
118
+ "total": 5,
119
+ "critical": 0,
120
+ "high": 0,
121
+ "medium": 5,
122
+ "low": 0,
123
+ "kev": 0,
124
+ "ids": [
125
+ "toctou-fs:agentic-security-audit.js:55",
126
+ "toctou-fs:agentic-security-consistency.js:44",
127
+ "toctou-fs:agentic-security-consistency.js:66",
128
+ "toctou-fs:agentic-security.js:1141",
129
+ "toctou-fs:agentic-security.js:362"
130
+ ]
131
+ },
132
+ {
133
+ "timestamp": "2026-05-28T14:16:39.437Z",
134
+ "label": "scan",
135
+ "total": 5,
136
+ "critical": 0,
137
+ "high": 0,
138
+ "medium": 5,
139
+ "low": 0,
140
+ "kev": 0,
141
+ "ids": [
142
+ "toctou-fs:agentic-security-audit.js:55",
143
+ "toctou-fs:agentic-security-consistency.js:44",
144
+ "toctou-fs:agentic-security-consistency.js:66",
145
+ "toctou-fs:agentic-security.js:1146",
146
+ "toctou-fs:agentic-security.js:367"
147
+ ]
148
+ },
149
+ {
150
+ "timestamp": "2026-05-28T14:16:53.162Z",
151
+ "label": "scan",
152
+ "total": 5,
153
+ "critical": 0,
154
+ "high": 0,
155
+ "medium": 5,
156
+ "low": 0,
157
+ "kev": 0,
158
+ "ids": [
159
+ "toctou-fs:agentic-security-audit.js:55",
160
+ "toctou-fs:agentic-security-consistency.js:44",
161
+ "toctou-fs:agentic-security-consistency.js:66",
162
+ "toctou-fs:agentic-security.js:1151",
163
+ "toctou-fs:agentic-security.js:367"
164
+ ]
114
165
  }
115
166
  ]
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "firstScanDate": "2026-05-26T04:00:10.482Z",
3
- "lastScanDate": "2026-05-27T11:23:11.268Z",
4
- "totalScans": 7,
5
- "daysCleanCritical": 2,
6
- "lastCleanDate": "2026-05-27",
3
+ "lastScanDate": "2026-05-28T14:16:53.192Z",
4
+ "totalScans": 10,
5
+ "daysCleanCritical": 3,
6
+ "lastCleanDate": "2026-05-28",
7
7
  "lastCriticalDate": null,
8
8
  "hasEverHadCritical": false,
9
- "bestDaysCleanCritical": 2,
9
+ "bestDaysCleanCritical": 3,
10
10
  "totalFindingsAtFirstScan": 11,
11
11
  "totalFindingsAtLastScan": 13,
12
12
  "totalFixesInferred": 0,
@@ -269,6 +269,11 @@ function renderV3Blocks(scan, flags) {
269
269
  // Always-on machine output (R2). Vibecoder gets JSON only; pro gets JSON+SARIF+CSV.
270
270
  async function writeMachineOutput(targetAbs, scan, meta, profile) {
271
271
  const stateDir = path.join(targetAbs, '.agentic-security');
272
+ const { isSafeStateDir: _isSafe } = await import('../src/posture/state-dir.js');
273
+ if (!_isSafe(stateDir)) {
274
+ if (process.env.AGENTIC_SECURITY_DEBUG === '1') process.stderr.write(`[agentic-security] refusing to write machine output at ${stateDir} — no project marker\n`);
275
+ return;
276
+ }
272
277
  await fsp.mkdir(stateDir, { recursive: true });
273
278
  // Always JSON (used by /security-fix and /security-report).
274
279
  await fsp.writeFile(path.join(stateDir, 'findings.json'),
@@ -495,14 +500,19 @@ async function cmdScan(args) {
495
500
  else process.stdout.write(body + '\n');
496
501
 
497
502
  // Persist last scan for /security-fix and /security-report
503
+ const { isSafeStateDir: _isSafeStateDir } = await import('../src/posture/state-dir.js');
498
504
  const stateDir = path.join(path.resolve(target), '.agentic-security');
499
- await fsp.mkdir(stateDir, { recursive: true });
500
- const persistedScan = toJSON(scan, meta);
501
- const lastScanBody = JSON.stringify(persistedScan, null, 2);
502
- await fsp.writeFile(path.join(stateDir, 'last-scan.json'), lastScanBody);
503
- try {
504
- await fsp.writeFile(path.join(stateDir, 'last-scan.json.sig'), _signLastScan(lastScanBody));
505
- } catch { /* non-fatal — sig file is best-effort */ }
505
+ if (_isSafeStateDir(stateDir)) {
506
+ await fsp.mkdir(stateDir, { recursive: true });
507
+ const persistedScan = toJSON(scan, meta);
508
+ const lastScanBody = JSON.stringify(persistedScan, null, 2);
509
+ await fsp.writeFile(path.join(stateDir, 'last-scan.json'), lastScanBody);
510
+ try {
511
+ await fsp.writeFile(path.join(stateDir, 'last-scan.json.sig'), _signLastScan(lastScanBody));
512
+ } catch { /* non-fatal — sig file is best-effort */ }
513
+ } else {
514
+ if (process.env.AGENTIC_SECURITY_DEBUG === '1') process.stderr.write(`[agentic-security] refusing to write state at ${stateDir} — no project marker in ${path.resolve(target)}\n`);
515
+ }
506
516
 
507
517
  // 0.14.0 — update streak / achievements after every full scan. Suppress
508
518
  // streak side effects when the user only wants raw JSON output (CI piping).
@@ -587,6 +597,11 @@ async function cmdCi(args) {
587
597
 
588
598
  // Persist the three CI artifacts.
589
599
  const stateDir = path.join(targetAbs, '.agentic-security');
600
+ const { isSafeStateDir: _isSafeCi } = await import('../src/posture/state-dir.js');
601
+ if (!_isSafeCi(stateDir)) {
602
+ if (process.env.AGENTIC_SECURITY_DEBUG === '1') process.stderr.write(`[agentic-security] refusing to write CI artifacts at ${stateDir} — no project marker\n`);
603
+ return;
604
+ }
590
605
  await fsp.mkdir(stateDir, { recursive: true });
591
606
  await fsp.writeFile(path.join(stateDir, 'findings.json'),
592
607
  JSON.stringify(toJSON(scan, meta), null, 2));
package/dist/178.index.js CHANGED
@@ -13,7 +13,7 @@ export const modules = {
13
13
  /* harmony import */ var node_child_process__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1421);
14
14
  /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3024);
15
15
  /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6760);
16
- /* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(8636);
16
+ /* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6048);
17
17
  // Time-travel + counterfactual scanning (v0.68).
18
18
  //
19
19
  // Two new modes that exploit the pure-input shape of runFullScan:
package/dist/384.index.js CHANGED
@@ -8,7 +8,7 @@ export const modules = {
8
8
  /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9
9
  /* harmony export */ scanCredentials: () => (/* reexport safe */ _engine_js__WEBPACK_IMPORTED_MODULE_0__.Sv)
10
10
  /* harmony export */ });
11
- /* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(8636);
11
+ /* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6048);
12
12
  // Secrets submodule view of the engine — credential + entropy + TODO scanning.
13
13
 
14
14
 
package/dist/476.index.js CHANGED
@@ -11,6 +11,7 @@ export const modules = {
11
11
  /* unused harmony export _internals */
12
12
  /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3024);
13
13
  /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6760);
14
+ /* harmony import */ var _state_dir_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1174);
14
15
  // Auto-rule synthesis from repeated FPs (FR-LEARN-6).
15
16
  //
16
17
  // Reads `.agentic-security/triage-feedback.json` (populated by the /triage
@@ -27,13 +28,11 @@ export const modules = {
27
28
 
28
29
 
29
30
 
30
- const TRIAGE_PATH = node_path__WEBPACK_IMPORTED_MODULE_1__.join('.agentic-security', 'triage-feedback.json');
31
- const PROPOSED_DIR = node_path__WEBPACK_IMPORTED_MODULE_1__.join('.agentic-security', 'rules-proposed');
32
31
 
33
32
  const DEFAULT_FP_THRESHOLD = 5;
34
33
 
35
34
  function _readTriage(scanRoot) {
36
- const fp = node_path__WEBPACK_IMPORTED_MODULE_1__.join(scanRoot || process.cwd(), TRIAGE_PATH);
35
+ const fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__/* .statePath */ .BQ)(scanRoot, 'triage-feedback.json');
37
36
  if (!node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(fp)) return null;
38
37
  try { return JSON.parse(node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(fp, 'utf8')); } catch { return null; }
39
38
  }
@@ -100,7 +99,8 @@ function synthesizeRules(scanRoot, opts = {}) {
100
99
  groups.get(k).push(e);
101
100
  }
102
101
  const proposals = [];
103
- const dir = node_path__WEBPACK_IMPORTED_MODULE_1__.join(scanRoot || process.cwd(), PROPOSED_DIR);
102
+ const dir = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__/* .statePath */ .BQ)(scanRoot, 'rules-proposed');
103
+ if (!opts.dryRun && !(0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.isSafeStateDir)(node_path__WEBPACK_IMPORTED_MODULE_1__.dirname(dir))) return [];
104
104
  for (const [, group] of groups) {
105
105
  if (group.length < threshold) continue;
106
106
  const summary = _summarizeGroup(group);
@@ -118,7 +118,7 @@ function synthesizeRules(scanRoot, opts = {}) {
118
118
  return proposals;
119
119
  }
120
120
 
121
- const _internals = { DEFAULT_FP_THRESHOLD, TRIAGE_PATH, PROPOSED_DIR };
121
+ const _internals = { DEFAULT_FP_THRESHOLD };
122
122
 
123
123
 
124
124
  /***/ })
package/dist/637.index.js CHANGED
@@ -10,7 +10,7 @@ export const modules = {
10
10
  /* harmony export */ renderPrDeltaText: () => (/* binding */ renderPrDeltaText)
11
11
  /* harmony export */ });
12
12
  /* harmony import */ var node_child_process__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1421);
13
- /* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(8636);
13
+ /* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6048);
14
14
  // Shadowscan / security-DELTA on PR (v0.72).
15
15
  //
16
16
  // Most SAST PR-comment integrations show absolute counts — "12 findings
@@ -0,0 +1,138 @@
1
+ export const id = 700;
2
+ export const ids = [700];
3
+ export const modules = {
4
+
5
+ /***/ 1700:
6
+ /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
+
8
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9
+ /* harmony export */ validateOsvFunctionsExist: () => (/* binding */ validateOsvFunctionsExist)
10
+ /* harmony export */ });
11
+ /* unused harmony export extractPythonPackageFunctions */
12
+ /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3024);
13
+ /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6760);
14
+ /* harmony import */ var node_child_process__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1421);
15
+ /* harmony import */ var _ir_parser_py_cst_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(682);
16
+ // Python package function extraction via the CST parser.
17
+ //
18
+ // Locates an installed Python package in site-packages or .venv,
19
+ // parses its source files via the Python CST parser, and returns
20
+ // a map of exported function names. Used by markUsedVulnFunctions
21
+ // to validate that OSV-named vulnerable functions actually exist
22
+ // in the installed version.
23
+
24
+
25
+
26
+
27
+
28
+
29
+ const VENV_DIRS = ['.venv', 'venv', '.env', 'env'];
30
+
31
+ function _findSitePackages(scanRoot) {
32
+ for (const vdir of VENV_DIRS) {
33
+ const base = node_path__WEBPACK_IMPORTED_MODULE_1__.join(scanRoot || '.', vdir);
34
+ if (!node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(base)) continue;
35
+ const lib = node_path__WEBPACK_IMPORTED_MODULE_1__.join(base, 'lib');
36
+ if (!node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(lib)) continue;
37
+ const pydirs = node_fs__WEBPACK_IMPORTED_MODULE_0__.readdirSync(lib).filter(d => d.startsWith('python'));
38
+ for (const pydir of pydirs) {
39
+ const sp = node_path__WEBPACK_IMPORTED_MODULE_1__.join(lib, pydir, 'site-packages');
40
+ if (node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(sp)) return sp;
41
+ }
42
+ }
43
+ // Fallback: ask python3 directly
44
+ try {
45
+ const out = (0,node_child_process__WEBPACK_IMPORTED_MODULE_2__.execFileSync)('python3', ['-c', 'import site; print(site.getsitepackages()[0])'], {
46
+ encoding: 'utf8', timeout: 5000,
47
+ }).trim();
48
+ if (out && node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(out)) return out;
49
+ } catch { /* no python3 or no site-packages */ }
50
+ return null;
51
+ }
52
+
53
+ function _findPackageDir(sitePackages, packageName) {
54
+ if (!sitePackages) return null;
55
+ const normalized = packageName.replace(/-/g, '_').toLowerCase();
56
+ const candidates = [
57
+ normalized,
58
+ packageName.toLowerCase(),
59
+ packageName,
60
+ ];
61
+ for (const name of candidates) {
62
+ const dir = node_path__WEBPACK_IMPORTED_MODULE_1__.join(sitePackages, name);
63
+ if (node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(dir) && node_fs__WEBPACK_IMPORTED_MODULE_0__.statSync(dir).isDirectory()) return dir;
64
+ }
65
+ return null;
66
+ }
67
+
68
+ function _readPyFilesFromDir(dir, maxFiles = 50) {
69
+ const entries = [];
70
+ try {
71
+ const files = node_fs__WEBPACK_IMPORTED_MODULE_0__.readdirSync(dir, { recursive: true })
72
+ .filter(f => f.endsWith('.py'))
73
+ .slice(0, maxFiles);
74
+ for (const f of files) {
75
+ const fp = node_path__WEBPACK_IMPORTED_MODULE_1__.join(dir, f);
76
+ try {
77
+ const content = node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(fp, 'utf8');
78
+ if (content.length < 1_000_000) {
79
+ entries.push({ file: f, content });
80
+ }
81
+ } catch { /* skip unreadable files */ }
82
+ }
83
+ } catch { /* dir not readable */ }
84
+ return entries;
85
+ }
86
+
87
+ function extractPythonPackageFunctions(packageName, scanRoot) {
88
+ const cap = (0,_ir_parser_py_cst_js__WEBPACK_IMPORTED_MODULE_3__/* .probePythonAvailable */ .w4)();
89
+ if (!cap.ok) return null;
90
+
91
+ const sitePackages = _findSitePackages(scanRoot);
92
+ const pkgDir = _findPackageDir(sitePackages, packageName);
93
+ if (!pkgDir) return null;
94
+
95
+ const pyFiles = _readPyFilesFromDir(pkgDir);
96
+ if (!pyFiles.length) return null;
97
+
98
+ const batch = (0,_ir_parser_py_cst_js__WEBPACK_IMPORTED_MODULE_3__/* .parsePythonFilesBatch */ .H2)(pyFiles);
99
+ if (!batch || !Array.isArray(batch)) return null;
100
+
101
+ const functionMap = new Map();
102
+ for (const fileIR of batch) {
103
+ if (!fileIR || !fileIR.functions) continue;
104
+ for (const fn of fileIR.functions) {
105
+ if (fn.name && !fn.name.startsWith('_')) {
106
+ functionMap.set(fn.name, {
107
+ file: fileIR.file,
108
+ line: fn.line,
109
+ qid: fn.qid,
110
+ params: fn.params,
111
+ });
112
+ }
113
+ }
114
+ }
115
+ return functionMap;
116
+ }
117
+
118
+ function validateOsvFunctionsExist(packageName, osvFunctions, scanRoot) {
119
+ if (!osvFunctions || !osvFunctions.length) return { validated: [], missing: [] };
120
+ const fnMap = extractPythonPackageFunctions(packageName, scanRoot);
121
+ if (!fnMap) return { validated: osvFunctions, missing: [] };
122
+ const validated = [];
123
+ const missing = [];
124
+ for (const fn of osvFunctions) {
125
+ const shortFn = fn.includes('.') ? fn.split('.').pop() : fn;
126
+ if (fnMap.has(shortFn) || fnMap.has(fn)) {
127
+ validated.push(shortFn);
128
+ } else {
129
+ missing.push(fn);
130
+ }
131
+ }
132
+ return { validated, missing };
133
+ }
134
+
135
+
136
+ /***/ })
137
+
138
+ };
package/dist/718.index.js CHANGED
@@ -97,6 +97,59 @@ function detectVendoredLibraries(fileContents) {
97
97
  }
98
98
  }
99
99
  }
100
+ // Pass 2: Function-body structural matching for minified/forked copies
101
+ const FUNCTION_BODY_SIGS = [
102
+ { pkg: 'lodash', ecosystem: 'npm', fn: 'merge', paramMin: 1,
103
+ bodyContains: ['assignValue', 'baseFor', 'isObject', 'baseMerge'] },
104
+ { pkg: 'lodash', ecosystem: 'npm', fn: 'template', paramMin: 1,
105
+ bodyContains: ['sourceURL', 'interpolate', 'evaluate', 'escape'] },
106
+ { pkg: 'lodash', ecosystem: 'npm', fn: 'defaultsDeep', paramMin: 1,
107
+ bodyContains: ['baseMerge', 'isMergeableObject', 'customDefaultsMerge'] },
108
+ { pkg: 'jquery', ecosystem: 'npm', fn: 'ajax', paramMin: 1,
109
+ bodyContains: ['XMLHttpRequest', 'ajaxSettings', 'crossDomain', 'responseFields'] },
110
+ { pkg: 'handlebars', ecosystem: 'npm', fn: 'compile', paramMin: 1,
111
+ bodyContains: ['templateSpec', 'container', 'invokePartial', 'blockParams'] },
112
+ { pkg: 'marked', ecosystem: 'npm', fn: 'parse', paramMin: 1,
113
+ bodyContains: ['Lexer', 'Parser', 'blockTokens', 'walkTokens'] },
114
+ { pkg: 'ejs', ecosystem: 'npm', fn: 'render', paramMin: 1,
115
+ bodyContains: ['includeFile', 'resolveInclude', 'rethrow', 'escapeFn'] },
116
+ { pkg: 'moment', ecosystem: 'npm', fn: 'format', paramMin: 0,
117
+ bodyContains: ['formatMoment', 'expandFormat', 'makeFormatFunction', 'localFormattingTokens'] },
118
+ { pkg: 'underscore', ecosystem: 'npm', fn: 'template', paramMin: 1,
119
+ bodyContains: ['interpolate', 'evaluate', 'escape', 'templateSettings'] },
120
+ { pkg: 'minimist', ecosystem: 'npm', fn: 'parse', paramMin: 1,
121
+ bodyContains: ['boolean', 'alias', 'default', 'stopEarly', 'unknown'] },
122
+ ];
123
+
124
+ for (const [fp, content] of Object.entries(fileContents)) {
125
+ if (!content || typeof content !== 'string') continue;
126
+ if (SKIP_DIRS.test(fp)) continue;
127
+ if (!/\.(?:js|mjs|cjs)$/i.test(fp)) continue;
128
+ if (content.length < 200 || content.length > 500_000) continue;
129
+
130
+ for (const sig of FUNCTION_BODY_SIGS) {
131
+ const key = `${sig.pkg}:${fp}`;
132
+ if (seen.has(key)) continue;
133
+ const fnRe = new RegExp(`(?:function\\s+${sig.fn}|(?:const|let|var)\\s+${sig.fn}\\s*=|${sig.fn}\\s*[:=]\\s*function)\\s*\\(`, 'g');
134
+ const m = fnRe.exec(content);
135
+ if (!m) continue;
136
+ const bodyWindow = content.slice(m.index, m.index + 2000);
137
+ const matchCount = sig.bodyContains.filter(kw => bodyWindow.includes(kw)).length;
138
+ if (matchCount < Math.ceil(sig.bodyContains.length * 0.6)) continue;
139
+ seen.add(key);
140
+ detected.push({
141
+ name: sig.pkg,
142
+ version: 'unknown',
143
+ ecosystem: sig.ecosystem,
144
+ file: fp,
145
+ scope: 'vendored',
146
+ isVendored: true,
147
+ _detectionMethod: 'function-body-signature',
148
+ _matchedKeywords: matchCount,
149
+ });
150
+ }
151
+ }
152
+
100
153
  return detected;
101
154
  }
102
155