@clear-capabilities/agentic-security-scanner 0.133.0 → 0.136.2
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/CHANGELOG.md +299 -0
- package/bin/agentic-security-audit.js +2 -1
- package/bin/agentic-security-consistency.js +2 -1
- package/bin/agentic-security.js +348 -40
- package/dist/113.index.js +6 -6
- package/dist/117.index.js +3 -1
- package/dist/178.index.js +1 -1
- package/dist/220.index.js +5 -3
- package/dist/238.index.js +4 -4
- package/dist/317.index.js +270 -0
- package/dist/384.index.js +1 -1
- package/dist/435.index.js +13 -7
- package/dist/499.index.js +86 -0
- package/dist/513.index.js +7 -3
- package/dist/526.index.js +6 -6
- package/dist/609.index.js +741 -0
- package/dist/637.index.js +1 -1
- package/dist/675.index.js +7 -5
- package/dist/839.index.js +4 -3
- package/dist/905.index.js +1173 -0
- package/dist/agentic-security.mjs +56 -56
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/dist/compliance-frameworks/ccpa.json +32 -0
- package/dist/compliance-frameworks/eu-ai-act.json +51 -0
- package/dist/compliance-frameworks/gdpr.json +45 -0
- package/dist/compliance-frameworks/hipaa-security-rule.json +56 -0
- package/dist/compliance-frameworks/nist-ai-600-1.json +51 -0
- package/dist/compliance-frameworks/nist-csf-2.json +73 -0
- package/dist/compliance-frameworks/nist-privacy-1-1.json +846 -0
- package/dist/compliance-frameworks/owasp-asvs-5.json +79 -0
- package/dist/compliance-frameworks/owasp-llm-top-10.json +69 -0
- package/package.json +14 -6
- package/src/badge.js +2 -1
- package/src/dataflow/cross-service-taint.js +2 -1
- package/src/dataflow/ifds-precise.js +6 -4
- package/src/dataflow/incremental.js +7 -5
- package/src/discovery/CLAUDE.md +48 -0
- package/src/discovery/confirm.js +47 -0
- package/src/discovery/disprove.js +79 -0
- package/src/discovery/hunter.js +116 -0
- package/src/discovery/index.js +331 -0
- package/src/discovery/judge.js +97 -0
- package/src/discovery/lenses.js +69 -0
- package/src/discovery/llm-invoke.js +120 -0
- package/src/discovery/memory.js +163 -0
- package/src/discovery/partition.js +92 -0
- package/src/engine.js +163 -7
- package/src/integrations/tickets.js +7 -6
- package/src/ir/ir-stats.js +1 -1
- package/src/ir/parser-cpp.js +1 -1
- package/src/leaderboard.js +3 -2
- package/src/llm-validator/consistency.js +6 -2
- package/src/llm-validator/index.js +30 -41
- package/src/llm-validator/providers.js +227 -0
- package/src/mcp/audit.js +2 -1
- package/src/mcp/tools.js +4 -3
- package/src/posture/CLAUDE.md +102 -1
- package/src/posture/accuracy-scorecard.js +59 -1
- package/src/posture/agents-memory.js +5 -3
- package/src/posture/auditor-walkthrough.js +9 -7
- package/src/posture/auth-posture-import.js +5 -4
- package/src/posture/autopilot.js +225 -0
- package/src/posture/comparison.js +181 -0
- package/src/posture/compliance-frameworks/nist-privacy-1-1.json +846 -0
- package/src/posture/compliance-policy.js +7 -9
- package/src/posture/custom-rules.js +7 -5
- package/src/posture/cve-alert-daemon.js +6 -5
- package/src/posture/dep-add-guard.js +2 -1
- package/src/posture/deterministic.js +3 -2
- package/src/posture/evidence-bundle.js +246 -0
- package/src/posture/execution-proof.js +25 -1
- package/src/posture/exploitability-probability.js +2 -1
- package/src/posture/feature-flags.js +3 -2
- package/src/posture/findings-memory.js +3 -3
- package/src/posture/fix-history.js +5 -2
- package/src/posture/fix-metrics.js +5 -5
- package/src/posture/fix-plan.js +2 -1
- package/src/posture/fleet.js +0 -0
- package/src/posture/grader-calibration.js +3 -4
- package/src/posture/intent-context.js +2 -1
- package/src/posture/learning.js +4 -3
- package/src/posture/license-attributions.js +5 -7
- package/src/posture/license-graph.js +2 -1
- package/src/posture/license-policy.js +2 -1
- package/src/posture/logic-claims.js +266 -0
- package/src/posture/model-rescan.js +4 -3
- package/src/posture/network-policy-import.js +3 -2
- package/src/posture/poc-inprocess.js +404 -2
- package/src/posture/pqc-migration-plan.js +7 -5
- package/src/posture/pr-augment.js +8 -5
- package/src/posture/privacy-framework.js +262 -0
- package/src/posture/proof-artifact.js +101 -0
- package/src/posture/prove-findings.js +28 -4
- package/src/posture/risk-dollars.js +2 -2
- package/src/posture/router.js +5 -4
- package/src/posture/ruleset-version.js +2 -2
- package/src/posture/runtime-correlation.js +2 -1
- package/src/posture/sbom-diff.js +12 -3
- package/src/posture/sca-policy.js +7 -4
- package/src/posture/scan-checkpoint.js +15 -0
- package/src/posture/state-dir.js +34 -0
- package/src/posture/telemetry-ingest.js +4 -3
- package/src/posture/threat-model-auto.js +4 -1
- package/src/posture/threat-model-grounding.js +11 -1
- package/src/posture/time-to-fix.js +3 -2
- package/src/posture/triage-memory.js +3 -2
- package/src/posture/waf-ingest.js +6 -5
- package/src/posture/watch-mode.js +4 -3
- package/src/report/index.js +9 -0
- package/src/sast/code-injection-multilang.js +29 -0
- package/src/sca/dep-confusion.js +2 -1
package/src/posture/state-dir.js
CHANGED
|
@@ -111,9 +111,42 @@ export function isSafeStateDir(dir) {
|
|
|
111
111
|
return false;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
// --- Read-only scanning (NON_MUTATING_SCAN_PRD S1) ---------------------------
|
|
115
|
+
//
|
|
116
|
+
// A scan is an OBSERVATION. Pointing the engine at a directory should not
|
|
117
|
+
// change it. Until this switch existed, `scan .` wrote seven-plus files into the
|
|
118
|
+
// scanned tree, which breaks CI that asserts a clean worktree, leaves artefacts
|
|
119
|
+
// in third-party code the user does not own, and — measured on this project's
|
|
120
|
+
// own benchmark — let a second scan read the first scan's conclusions as source.
|
|
121
|
+
//
|
|
122
|
+
// This is deliberately a KILL SWITCH AT THE SEAM rather than a parameter
|
|
123
|
+
// threaded through 72 call sites. Threading it would mean 72 chances to forget,
|
|
124
|
+
// and the module this lives in exists precisely because that kind of
|
|
125
|
+
// per-caller discipline already failed once: its header records a user who
|
|
126
|
+
// uninstalled the plugin after stray state directories broke their build.
|
|
127
|
+
let _stateWritesEnabled = true;
|
|
128
|
+
|
|
129
|
+
/** Turn all state writing off (or back on) for this process. */
|
|
130
|
+
export function setStateWritesEnabled(enabled) {
|
|
131
|
+
_stateWritesEnabled = Boolean(enabled);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* False when writing is disabled by the CLI flag or the environment.
|
|
136
|
+
*
|
|
137
|
+
* The env var is read at CALL time, not captured at import, so a test or a
|
|
138
|
+
* caller can set it after the module is loaded — the same mistake that made the
|
|
139
|
+
* gate verdict cache silently never engage.
|
|
140
|
+
*/
|
|
141
|
+
export function stateWritesEnabled() {
|
|
142
|
+
if (process.env.AGENTIC_SECURITY_NO_STATE === '1') return false;
|
|
143
|
+
return _stateWritesEnabled;
|
|
144
|
+
}
|
|
145
|
+
|
|
114
146
|
// Safe mkdir: only creates .agentic-security/ if the parent has a project marker.
|
|
115
147
|
// Returns the dir on success, null if refused. Logs a warning when refused.
|
|
116
148
|
export function ensureStateDir(scanRoot) {
|
|
149
|
+
if (!stateWritesEnabled()) return null;
|
|
117
150
|
const dir = stateDir(scanRoot);
|
|
118
151
|
if (!isSafeStateDir(dir)) {
|
|
119
152
|
if (process.env.AGENTIC_SECURITY_DEBUG === '1') {
|
|
@@ -132,6 +165,7 @@ export function ensureStateDir(scanRoot) {
|
|
|
132
165
|
// Safe write: only writes if isSafeStateDir(parent) returns true.
|
|
133
166
|
// Returns true on success, false if refused or errored.
|
|
134
167
|
export function safeWriteState(filePath, content) {
|
|
168
|
+
if (!stateWritesEnabled()) return false;
|
|
135
169
|
const dir = path.dirname(filePath);
|
|
136
170
|
if (!isSafeStateDir(dir)) {
|
|
137
171
|
if (process.env.AGENTIC_SECURITY_DEBUG === '1') {
|
|
@@ -26,9 +26,10 @@
|
|
|
26
26
|
import * as fs from 'node:fs';
|
|
27
27
|
import * as path from 'node:path';
|
|
28
28
|
|
|
29
|
+
import { statePath } from './state-dir.js';
|
|
29
30
|
const CANDIDATE_PATHS = [
|
|
30
|
-
'
|
|
31
|
-
'
|
|
31
|
+
'telemetry.json',
|
|
32
|
+
'prod-telemetry.json',
|
|
32
33
|
];
|
|
33
34
|
|
|
34
35
|
const HOT_THRESHOLD = 1000; // requests / window — promotes to hot
|
|
@@ -37,7 +38,7 @@ const COLD_THRESHOLD = 0; // exactly zero requests → cold
|
|
|
37
38
|
export function loadTelemetry(scanRoot) {
|
|
38
39
|
const root = scanRoot || process.cwd();
|
|
39
40
|
for (const rel of CANDIDATE_PATHS) {
|
|
40
|
-
const fp =
|
|
41
|
+
const fp = statePath(root, rel);
|
|
41
42
|
if (!fs.existsSync(fp)) continue;
|
|
42
43
|
try {
|
|
43
44
|
const data = JSON.parse(fs.readFileSync(fp, 'utf8'));
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
// .agentic-security/threat-model.md (human-readable).
|
|
24
24
|
|
|
25
25
|
import * as fs from 'node:fs';
|
|
26
|
+
import { stateDir, stateWritesEnabled } from './state-dir.js';
|
|
26
27
|
import * as path from 'node:path';
|
|
27
28
|
|
|
28
29
|
// STRIDE category descriptors
|
|
@@ -211,7 +212,9 @@ function _mid(id) { return String(id).replace(/[^A-Za-z0-9]/g, '_').slice(0, 60)
|
|
|
211
212
|
* Persist threat model to disk: JSON for tooling, Markdown for review.
|
|
212
213
|
*/
|
|
213
214
|
export function persistThreatModel(scanRoot, model) {
|
|
214
|
-
|
|
215
|
+
// NON_MUTATING_SCAN_PRD S1 — a scan must not modify the tree it scans.
|
|
216
|
+
if (!stateWritesEnabled()) return;
|
|
217
|
+
const dir = stateDir(scanRoot);
|
|
215
218
|
try { fs.mkdirSync(dir, { recursive: true }); } catch {}
|
|
216
219
|
try { fs.writeFileSync(path.join(dir, 'threat-model.json'), JSON.stringify(model, null, 2)); } catch {}
|
|
217
220
|
try { fs.writeFileSync(path.join(dir, 'threat-model.md'), renderMarkdown(model)); } catch {}
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
import * as fs from 'node:fs';
|
|
22
22
|
import * as path from 'node:path';
|
|
23
23
|
|
|
24
|
+
import { statePath } from './state-dir.js';
|
|
24
25
|
const DOC_PATHS = [
|
|
25
26
|
'CLAUDE.md',
|
|
26
27
|
'docs/THREAT-MODEL.md',
|
|
@@ -31,7 +32,16 @@ const DOC_PATHS = [
|
|
|
31
32
|
];
|
|
32
33
|
|
|
33
34
|
function _readDoc(scanRoot, rel) {
|
|
34
|
-
|
|
35
|
+
// DOC_PATHS is a MIXED list: most entries are ordinary repository paths
|
|
36
|
+
// (CLAUDE.md, docs/THREAT-MODEL.md) and exactly one lives under the state
|
|
37
|
+
// directory. Routing the whole list through statePath() made every document
|
|
38
|
+
// resolve to `.agentic-security/<name>` and broke threat-model loading
|
|
39
|
+
// entirely — five tests caught it. Only the state entry uses the seam.
|
|
40
|
+
const STATE_PREFIX = '.agentic-security/';
|
|
41
|
+
const fp = rel.startsWith(STATE_PREFIX)
|
|
42
|
+
? statePath(scanRoot, rel.slice(STATE_PREFIX.length))
|
|
43
|
+
: path.join(scanRoot, rel);
|
|
44
|
+
try { return fs.readFileSync(fp, 'utf8'); } catch { return ''; }
|
|
35
45
|
}
|
|
36
46
|
|
|
37
47
|
function _allDocs(scanRoot) {
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
import * as fs from 'node:fs';
|
|
14
14
|
import * as path from 'node:path';
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
|
|
17
|
+
import { statePath } from './state-dir.js';
|
|
17
18
|
const HISTORY_FILE = 'fix-history/log.json';
|
|
18
19
|
|
|
19
20
|
// Family base estimates (hours). Tuned from typical patch shapes.
|
|
@@ -50,7 +51,7 @@ const FAMILY_BASE_HOURS = {
|
|
|
50
51
|
};
|
|
51
52
|
|
|
52
53
|
function _loadFixHistory(scanRoot) {
|
|
53
|
-
const fp =
|
|
54
|
+
const fp = statePath(scanRoot, HISTORY_FILE);
|
|
54
55
|
if (!fs.existsSync(fp)) return [];
|
|
55
56
|
try {
|
|
56
57
|
const arr = JSON.parse(fs.readFileSync(fp, 'utf8'));
|
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
import * as fs from 'node:fs';
|
|
22
22
|
import * as path from 'node:path';
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
import { stateDir, stateWritesEnabled } from './state-dir.js';
|
|
25
25
|
const MEMORY_FILE = 'triage-memory.jsonl';
|
|
26
26
|
const AGENTS_FILE = 'AGENTS.md';
|
|
27
27
|
|
|
28
|
-
function _stateDir(scanRoot) { return
|
|
28
|
+
function _stateDir(scanRoot) { return stateDir(scanRoot); }
|
|
29
29
|
function _memPath(scanRoot) { return path.join(_stateDir(scanRoot), MEMORY_FILE); }
|
|
30
30
|
function _agentsPath(scanRoot) { return path.join(_stateDir(scanRoot), AGENTS_FILE); }
|
|
31
31
|
|
|
@@ -43,6 +43,7 @@ function _bucketKey(finding) {
|
|
|
43
43
|
export function recordDecision(scanRoot, finding, decision, reason) {
|
|
44
44
|
if (!scanRoot || !finding || !decision) return null;
|
|
45
45
|
if (!['wont-fix', 'false-positive'].includes(decision)) return null;
|
|
46
|
+
if (!stateWritesEnabled()) return false;
|
|
46
47
|
try { fs.mkdirSync(_stateDir(scanRoot), { recursive: true }); } catch {}
|
|
47
48
|
|
|
48
49
|
const entry = {
|
|
@@ -36,11 +36,12 @@
|
|
|
36
36
|
import * as fs from 'node:fs';
|
|
37
37
|
import * as path from 'node:path';
|
|
38
38
|
|
|
39
|
+
import { statePath } from './state-dir.js';
|
|
39
40
|
const CANDIDATE_PATHS = [
|
|
40
|
-
'
|
|
41
|
-
'
|
|
42
|
-
'
|
|
43
|
-
'
|
|
41
|
+
'waf-rules.json',
|
|
42
|
+
'waf-rules.yml',
|
|
43
|
+
'waf-rules.yaml',
|
|
44
|
+
'waf-rules.conf',
|
|
44
45
|
'waf/rules.json',
|
|
45
46
|
'cloudflare-rules.json',
|
|
46
47
|
'aws-waf.json',
|
|
@@ -134,7 +135,7 @@ function parseScalar(s) {
|
|
|
134
135
|
export function loadWafRules(scanRoot) {
|
|
135
136
|
const root = scanRoot || process.cwd();
|
|
136
137
|
for (const rel of CANDIDATE_PATHS) {
|
|
137
|
-
const fp =
|
|
138
|
+
const fp = statePath(root, rel);
|
|
138
139
|
if (!fs.existsSync(fp)) continue;
|
|
139
140
|
let text;
|
|
140
141
|
try { text = fs.readFileSync(fp, 'utf8'); } catch { continue; }
|
|
@@ -23,7 +23,7 @@ import * as fs from 'node:fs/promises';
|
|
|
23
23
|
import * as fsSync from 'node:fs';
|
|
24
24
|
import * as path from 'node:path';
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
import { stateDir, statePath, stateWritesEnabled } from './state-dir.js';
|
|
27
27
|
const STATUS_MD = 'watch-status.md';
|
|
28
28
|
const STATUS_JSON = 'watch-status.json';
|
|
29
29
|
const DEBOUNCE_MS = 350;
|
|
@@ -85,7 +85,8 @@ export function renderStatusLine(delta) {
|
|
|
85
85
|
* Persist watch-status.{md,json}. Cheap atomic write (write tmp, rename).
|
|
86
86
|
*/
|
|
87
87
|
export function persistStatus(scanRoot, delta) {
|
|
88
|
-
const dir =
|
|
88
|
+
const dir = stateDir(scanRoot);
|
|
89
|
+
if (!stateWritesEnabled()) return;
|
|
89
90
|
try { fsSync.mkdirSync(dir, { recursive: true }); } catch {}
|
|
90
91
|
const status = {
|
|
91
92
|
ts: new Date().toISOString(),
|
|
@@ -122,7 +123,7 @@ export function persistStatus(scanRoot, delta) {
|
|
|
122
123
|
* Read the latest watch-status (returns null if none).
|
|
123
124
|
*/
|
|
124
125
|
export function readStatus(scanRoot) {
|
|
125
|
-
return _readJsonSafe(
|
|
126
|
+
return _readJsonSafe(statePath(scanRoot, STATUS_JSON));
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
/**
|
package/src/report/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import * as crypto from 'node:crypto';
|
|
|
3
3
|
import { _isCustomSuppressed } from '../engine.js';
|
|
4
4
|
import { alertFace, approveFace } from './mascot.js';
|
|
5
5
|
import { SCANNER_VERSION } from '../posture/version.js';
|
|
6
|
+
import { proofBlock } from '../posture/proof-artifact.js';
|
|
6
7
|
|
|
7
8
|
const SEV_RANK = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
|
|
8
9
|
const SEV_TO_SARIF = { critical: 'error', high: 'error', medium: 'warning', low: 'note', info: 'none' };
|
|
@@ -709,6 +710,14 @@ export function toSARIF(scan, meta={}){
|
|
|
709
710
|
// (verified | unsigned | pass-through). The legacy bool flags are
|
|
710
711
|
// emitted alongside for one release of grace so existing dashboards
|
|
711
712
|
// don't break; new integrations should switch to signatureStatus.
|
|
713
|
+
// PRD Epic 1.4 / 7.4 — the proof block. `proofLevel` is the
|
|
714
|
+
// reader-facing vocabulary (PROVEN / PROBABLE_FP / REACHABLE /
|
|
715
|
+
// PATTERN); `proofArtifactSha256` commits to the evidence that
|
|
716
|
+
// justified it, so a fix PR can reference the artifact it was
|
|
717
|
+
// reviewed against. Omitted entirely when the proof stage did not
|
|
718
|
+
// run — labelling every finding PATTERN would assert each was
|
|
719
|
+
// considered and found unprovable.
|
|
720
|
+
...(proofBlock(f) || {}),
|
|
712
721
|
signatureStatus: f.signatureStatus || (f._passThroughSigning ? 'pass-through' : (f._unsigned ? 'unsigned' : 'verified')),
|
|
713
722
|
...(f._unsigned ? { unsigned: true } : {}),
|
|
714
723
|
...(f._passThroughSigning ? { passThroughSigning: true } : {}),
|
|
@@ -64,6 +64,34 @@ function patternsFor(lang) {
|
|
|
64
64
|
gate: /\b(?:DataTable|DataColumn|DataView)\b/ },
|
|
65
65
|
];
|
|
66
66
|
}
|
|
67
|
+
if (lang === 'js') {
|
|
68
|
+
// Server-side JS code injection. Measured gap, not a speculative one: on the
|
|
69
|
+
// independent population CWE-94 scored 0/6, and the missed sink in
|
|
70
|
+
// GHSA-3769-jgqc-cxm7 was `vm.run(\`...${code}...\`)` — the NodeVM / vm2
|
|
71
|
+
// sandbox family. `eval` and `new Function` were already covered elsewhere
|
|
72
|
+
// (client-side.js, agent-untrusted-flow.js); a library whose entire purpose
|
|
73
|
+
// is executing supplied code was not covered anywhere.
|
|
74
|
+
//
|
|
75
|
+
// A SANDBOX IS NOT A CONTROL HERE. vm/vm2 are routinely reached for as the
|
|
76
|
+
// "safe" way to run untrusted code, and both have a long history of escapes.
|
|
77
|
+
// Running attacker-influenced source inside one is still code injection, so
|
|
78
|
+
// these fire on a non-literal argument exactly like every other sink in this
|
|
79
|
+
// module.
|
|
80
|
+
return [
|
|
81
|
+
// vm.run / NodeVM#run / VM#run — the vm2 and @n8n/vm2 family.
|
|
82
|
+
{ key: 'vm-run', re: new RegExp(String.raw`\.\s*run\s*\(\s*${notLiteral}`, 'g'),
|
|
83
|
+
gate: /\b(?:NodeVM|VM|vm2|createContext|new\s+VM\b)\b/ },
|
|
84
|
+
// node:vm's own evaluators. runInNewContext takes the code first.
|
|
85
|
+
{ key: 'vm-run-in-context',
|
|
86
|
+
re: new RegExp(String.raw`\.\s*(?:runInNewContext|runInThisContext|runInContext)\s*\(\s*${notLiteral}`, 'g') },
|
|
87
|
+
// new vm.Script(src) compiles before it runs; the injection is here.
|
|
88
|
+
{ key: 'vm-script', re: new RegExp(String.raw`\bnew\s+(?:vm\s*\.\s*)?Script\s*\(\s*${notLiteral}`, 'g'),
|
|
89
|
+
gate: /\b(?:vm|node:vm)\b/ },
|
|
90
|
+
// Server-side eval / Function, which client-side.js only covers for browsers.
|
|
91
|
+
{ key: 'js-eval', re: new RegExp(String.raw`(?:^|[^.\w])eval\s*\(\s*${notLiteral}`, 'gm') },
|
|
92
|
+
{ key: 'js-new-function', re: new RegExp(String.raw`\bnew\s+Function\s*\(\s*${notLiteral}`, 'g') },
|
|
93
|
+
];
|
|
94
|
+
}
|
|
67
95
|
if (lang === 'go') {
|
|
68
96
|
return [
|
|
69
97
|
// yaegi / gomacro interpreter eval of a non-literal.
|
|
@@ -82,6 +110,7 @@ function _lang(fp) {
|
|
|
82
110
|
if (/\.kt$/i.test(fp)) return 'kt';
|
|
83
111
|
if (/\.cs$/i.test(fp)) return 'cs';
|
|
84
112
|
if (/\.go$/i.test(fp)) return 'go';
|
|
113
|
+
if (/\.(?:js|jsx|mjs|cjs|ts|tsx)$/i.test(fp)) return 'js';
|
|
85
114
|
return null;
|
|
86
115
|
}
|
|
87
116
|
|
package/src/sca/dep-confusion.js
CHANGED
|
@@ -12,6 +12,7 @@ import * as path from 'node:path';
|
|
|
12
12
|
import * as yaml from '../util/yaml.js';
|
|
13
13
|
import { createRequire } from 'node:module';
|
|
14
14
|
|
|
15
|
+
import { statePath } from '../posture/state-dir.js';
|
|
15
16
|
const _require = createRequire(import.meta.url);
|
|
16
17
|
const _POPULAR = (() => {
|
|
17
18
|
try {
|
|
@@ -52,7 +53,7 @@ export function levenshtein(a, b, maxDistance = 2) {
|
|
|
52
53
|
function _loadInternalScopes(scanRoot) {
|
|
53
54
|
if (!scanRoot) return [];
|
|
54
55
|
for (const name of ['internal-scopes.yml', 'internal-scopes.yaml']) {
|
|
55
|
-
const p =
|
|
56
|
+
const p = statePath(scanRoot, name);
|
|
56
57
|
if (!fs.existsSync(p)) continue;
|
|
57
58
|
try {
|
|
58
59
|
const doc = yaml.load(fs.readFileSync(p, 'utf8'));
|