@clear-capabilities/agentic-security-scanner 0.79.0 → 0.84.1
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/178.index.js +1 -1
- package/dist/333.index.js +283 -0
- package/dist/384.index.js +1 -1
- package/dist/637.index.js +1 -1
- package/dist/838.index.js +1 -1
- package/dist/839.index.js +170 -0
- package/dist/985.index.js +140 -1
- package/dist/agentic-security.mjs +10 -10
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/package.json +7 -5
- package/src/.agentic-security/findings.json +117732 -0
- package/src/.agentic-security/last-scan.json +117732 -0
- package/src/.agentic-security/last-scan.json.sig +1 -0
- package/src/.agentic-security/scan-history.json +12946 -0
- package/src/.agentic-security/streak.json +21 -0
- package/src/dataflow/.agentic-security/findings.json +6086 -0
- package/src/dataflow/.agentic-security/last-scan.json +6086 -0
- package/src/dataflow/.agentic-security/last-scan.json.sig +1 -0
- package/src/dataflow/.agentic-security/scan-history.json +250 -0
- package/src/dataflow/.agentic-security/streak.json +21 -0
- package/src/dataflow/cross-service-taint.js +201 -0
- package/src/dataflow/formal-verify.js +204 -0
- package/src/dataflow/ifds-precise.js +222 -0
- package/src/dataflow/k2-summary-cache.js +153 -0
- package/src/dataflow/lib-taint-summaries.js +198 -0
- package/src/dataflow/privacy-taint.js +205 -0
- package/src/dataflow/smt-feasibility.js +189 -0
- package/src/engine.js +825 -127
- package/src/ir/.agentic-security/findings.json +4011 -0
- package/src/ir/.agentic-security/last-scan.json +4011 -0
- package/src/ir/.agentic-security/last-scan.json.sig +1 -0
- package/src/ir/.agentic-security/scan-history.json +193 -0
- package/src/ir/.agentic-security/streak.json +20 -0
- package/src/ir/cpp-preprocessor.js +142 -0
- package/src/ir/csharp-ir.js +604 -0
- package/src/ir/universal-ir.js +403 -0
- package/src/mcp/.agentic-security/findings.json +8632 -0
- package/src/mcp/.agentic-security/last-scan.json +8632 -0
- package/src/mcp/.agentic-security/last-scan.json.sig +1 -0
- package/src/mcp/.agentic-security/scan-history.json +331 -0
- package/src/mcp/.agentic-security/streak.json +20 -0
- package/src/mcp/tools.js +140 -1
- package/src/posture/.agentic-security/findings.json +77181 -0
- package/src/posture/.agentic-security/last-scan.json +77181 -0
- package/src/posture/.agentic-security/last-scan.json.sig +1 -0
- package/src/posture/.agentic-security/scan-history.json +8904 -0
- package/src/posture/.agentic-security/streak.json +21 -0
- package/src/posture/api-contract.js +193 -0
- package/src/posture/attack-taxonomy.js +227 -0
- package/src/posture/auditor-walkthrough.js +252 -0
- package/src/posture/claude-authorship.js +197 -0
- package/src/posture/compliance-frameworks/.agentic-security/findings.json +80 -0
- package/src/posture/compliance-frameworks/.agentic-security/last-scan.json +80 -0
- package/src/posture/compliance-frameworks/.agentic-security/last-scan.json.sig +1 -0
- package/src/posture/compliance-frameworks/.agentic-security/scan-history.json +90 -0
- package/src/posture/compliance-frameworks/.agentic-security/streak.json +22 -0
- package/src/posture/compliance-frameworks/ccpa.json +32 -0
- package/src/posture/compliance-frameworks/eu-ai-act.json +51 -0
- package/src/posture/compliance-frameworks/gdpr.json +45 -0
- package/src/posture/compliance-frameworks/hipaa-security-rule.json +56 -0
- package/src/posture/compliance-frameworks/nist-ai-600-1.json +51 -0
- package/src/posture/compliance-frameworks/nist-csf-2.json +73 -0
- package/src/posture/compliance-frameworks/owasp-asvs-5.json +79 -0
- package/src/posture/compliance-frameworks/owasp-llm-top-10.json +69 -0
- package/src/posture/compliance-policy.js +218 -0
- package/src/posture/composite-risk.js +122 -0
- package/src/posture/cross-repo-memory.js +180 -0
- package/src/posture/csharp-analysis.js +330 -0
- package/src/posture/dep-add-guard.js +197 -0
- package/src/posture/exploit-bundle.js +210 -0
- package/src/posture/federated-learning.js +172 -0
- package/src/posture/findings-memory.js +152 -0
- package/src/posture/fix-style-mirror.js +118 -0
- package/src/posture/git-history.js +141 -0
- package/src/posture/intent-context.js +175 -0
- package/src/posture/license-attributions.js +94 -0
- package/src/posture/license-graph.js +238 -0
- package/src/posture/model-rescan.js +76 -0
- package/src/posture/pattern-propagation.js +39 -0
- package/src/posture/pqc-migration-plan.js +158 -0
- package/src/posture/pr-augment.js +234 -0
- package/src/posture/reachability-filter.js +33 -2
- package/src/posture/realtime-cve-monitor.js +214 -0
- package/src/posture/risk-dollars.js +158 -0
- package/src/posture/runtime-correlation.js +174 -0
- package/src/posture/sbom-diff.js +171 -0
- package/src/posture/sca-policy.js +235 -0
- package/src/posture/sca-upgrade.js +259 -0
- package/src/posture/threat-model-auto.js +268 -0
- package/src/posture/threat-model-grounding.js +169 -0
- package/src/posture/time-to-fix.js +129 -0
- package/src/posture/triage-learning.js +170 -0
- package/src/posture/triage-memory.js +151 -0
- package/src/posture/triage.js +40 -1
- package/src/posture/watch-mode.js +171 -0
- package/src/posture/workflow-installer.js +231 -0
- package/src/sast/.agentic-security/findings.json +6154 -0
- package/src/sast/.agentic-security/last-scan.json +6154 -0
- package/src/sast/.agentic-security/last-scan.json.sig +1 -0
- package/src/sast/.agentic-security/scan-history.json +941 -0
- package/src/sast/.agentic-security/streak.json +22 -0
- package/src/sast/_secret-entropy.js +145 -0
- package/src/sast/cloud-iam.js +312 -0
- package/src/sast/cpp.js +138 -4
- package/src/sast/crypto-protocol.js +388 -0
- package/src/sast/csharp-tokenizer.js +392 -0
- package/src/sast/csharp.js +924 -138
- package/src/sast/dapp-frontend.js +200 -0
- package/src/sast/k8s-admission.js +271 -0
- package/src/sast/llm-app.js +272 -0
- package/src/sast/ml-supply-chain.js +259 -0
- package/src/sast/mobile.js +224 -0
- package/src/sast/post-quantum-crypto.js +348 -0
- package/src/sast/web3-advanced.js +375 -0
- package/src/sca/.agentic-security/findings.json +7460 -0
- package/src/sca/.agentic-security/last-scan.json +7460 -0
- package/src/sca/.agentic-security/last-scan.json.sig +1 -0
- package/src/sca/.agentic-security/scan-history.json +113 -0
- package/src/sca/.agentic-security/streak.json +21 -0
- package/src/sca/CLAUDE.md +161 -0
- package/src/sca/binary-metadata.js +37 -15
- package/src/sca/sigstore-verify.js +215 -0
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__(
|
|
16
|
+
/* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(2271);
|
|
17
17
|
// Time-travel + counterfactual scanning (v0.68).
|
|
18
18
|
//
|
|
19
19
|
// Two new modes that exploit the pure-input shape of runFullScan:
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
export const id = 333;
|
|
2
|
+
export const ids = [333];
|
|
3
|
+
export const modules = {
|
|
4
|
+
|
|
5
|
+
/***/ 5333:
|
|
6
|
+
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
|
7
|
+
|
|
8
|
+
__webpack_require__.r(__webpack_exports__);
|
|
9
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
10
|
+
/* harmony export */ applyScaUpgrade: () => (/* binding */ applyScaUpgrade),
|
|
11
|
+
/* harmony export */ planScaUpgrade: () => (/* binding */ planScaUpgrade)
|
|
12
|
+
/* harmony export */ });
|
|
13
|
+
/* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3024);
|
|
14
|
+
/* harmony import */ var node_fs_promises__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(1455);
|
|
15
|
+
/* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6760);
|
|
16
|
+
/* harmony import */ var node_crypto__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(7598);
|
|
17
|
+
/* harmony import */ var node_child_process__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(1421);
|
|
18
|
+
/* harmony import */ var node_util__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(7975);
|
|
19
|
+
/* harmony import */ var _state_dir_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(1174);
|
|
20
|
+
// SCA upgrade engine: dry-run plan + worktree-isolated apply.
|
|
21
|
+
//
|
|
22
|
+
// Phase 3 / Item 5 of the SCA improvement plan. The MCP `apply_fix` path
|
|
23
|
+
// refuses to write manifest files (package.json, *-lock.*, poetry.lock,
|
|
24
|
+
// Cargo.lock, etc.) for safety. SCA findings need a separate path that:
|
|
25
|
+
// 1. Generates an upgrade *plan* via the ecosystem's native dry-run
|
|
26
|
+
// command (npm install --dry-run, pip install --dry-run, etc.).
|
|
27
|
+
// 2. Applies the upgrade via the package manager itself, with a backup
|
|
28
|
+
// + test-gate so a peer-dep break or test regression rolls back.
|
|
29
|
+
//
|
|
30
|
+
// Caller pattern: plan first (read-only), inspect the breaking-change
|
|
31
|
+
// flag / peer warnings, then apply with confirm:true.
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
const execFileAsync = (0,node_util__WEBPACK_IMPORTED_MODULE_5__.promisify)(node_child_process__WEBPACK_IMPORTED_MODULE_4__.execFile);
|
|
42
|
+
|
|
43
|
+
// Per-ecosystem command/manifest map. Add ecosystems by extending this
|
|
44
|
+
// table — every other place in the module reads it.
|
|
45
|
+
const ECOSYSTEM = {
|
|
46
|
+
npm: {
|
|
47
|
+
manifests: ['package.json', 'package-lock.json'],
|
|
48
|
+
altManifests: [['yarn.lock'], ['pnpm-lock.yaml']],
|
|
49
|
+
dryRun: (pkg, ver) => ({ cmd: 'npm', args: ['install', `${pkg}@${ver}`, '--dry-run', '--json'] }),
|
|
50
|
+
apply: (pkg, ver) => ({ cmd: 'npm', args: ['install', `${pkg}@${ver}`, '--save'] }),
|
|
51
|
+
parseDryRun(stdout) {
|
|
52
|
+
try {
|
|
53
|
+
const j = JSON.parse(stdout);
|
|
54
|
+
const peerDeps = Array.isArray(j.warnings) ? j.warnings.filter(w => /peer dep/i.test(w)) : [];
|
|
55
|
+
const transitiveImpact = (j.added || []).length + (j.updated || []).length + (j.removed || []).length;
|
|
56
|
+
return { peerDeps, transitiveImpact, rawSummary: { added: (j.added || []).length, updated: (j.updated || []).length, removed: (j.removed || []).length } };
|
|
57
|
+
} catch { return { peerDeps: [], transitiveImpact: 0, rawSummary: null }; }
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
pypi: {
|
|
61
|
+
manifests: ['requirements.txt', 'pyproject.toml'],
|
|
62
|
+
altManifests: [['poetry.lock'], ['Pipfile.lock']],
|
|
63
|
+
dryRun: (pkg, ver) => ({ cmd: 'pip', args: ['install', '--dry-run', `${pkg}==${ver}`] }),
|
|
64
|
+
apply: (pkg, ver) => ({ cmd: 'pip', args: ['install', '--upgrade', `${pkg}==${ver}`] }),
|
|
65
|
+
parseDryRun() {
|
|
66
|
+
// pip --dry-run output is human-readable; we don't parse it for v1.
|
|
67
|
+
return { peerDeps: [], transitiveImpact: 0, rawSummary: null };
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
cargo: {
|
|
71
|
+
manifests: ['Cargo.toml', 'Cargo.lock'],
|
|
72
|
+
altManifests: [],
|
|
73
|
+
dryRun: (pkg, _ver) => ({ cmd: 'cargo', args: ['update', '--package', pkg, '--dry-run'] }),
|
|
74
|
+
apply: (pkg, _ver) => ({ cmd: 'cargo', args: ['update', '--package', pkg] }),
|
|
75
|
+
parseDryRun() { return { peerDeps: [], transitiveImpact: 0, rawSummary: null }; },
|
|
76
|
+
},
|
|
77
|
+
golang: {
|
|
78
|
+
manifests: ['go.mod', 'go.sum'],
|
|
79
|
+
altManifests: [],
|
|
80
|
+
dryRun: (_pkg, _ver) => null, // `go get` has no dry-run flag; we skip dry-run in v1.
|
|
81
|
+
apply: (pkg, ver) => ({ cmd: 'go', args: ['get', `${pkg}@v${ver}`] }),
|
|
82
|
+
parseDryRun() { return { peerDeps: [], transitiveImpact: 0, rawSummary: null }; },
|
|
83
|
+
},
|
|
84
|
+
// Other ecosystems (rubygems, packagist, pub, maven) return a structured
|
|
85
|
+
// "manual" plan in v1 — no native dry-run + the install side-effects
|
|
86
|
+
// (gem build artifacts, composer cache) are easier for the user to
|
|
87
|
+
// confirm interactively.
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
function _majorVersion(v) {
|
|
91
|
+
const m = String(v || '').match(/^(\d+)/);
|
|
92
|
+
return m ? parseInt(m[1], 10) : null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function _detectTestCommand(scanRoot) {
|
|
96
|
+
try {
|
|
97
|
+
const pkg = node_path__WEBPACK_IMPORTED_MODULE_2__.join(scanRoot, 'package.json');
|
|
98
|
+
if (node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(pkg)) {
|
|
99
|
+
const j = JSON.parse(node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(pkg, 'utf8'));
|
|
100
|
+
if (j.scripts?.test && !/no test specified/i.test(j.scripts.test)) {
|
|
101
|
+
return { cmd: 'npm', args: ['test'] };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} catch {}
|
|
105
|
+
if (node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(node_path__WEBPACK_IMPORTED_MODULE_2__.join(scanRoot, 'Cargo.toml'))) return { cmd: 'cargo', args: ['test'] };
|
|
106
|
+
if (node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(node_path__WEBPACK_IMPORTED_MODULE_2__.join(scanRoot, 'go.mod'))) return { cmd: 'go', args: ['test', './...'] };
|
|
107
|
+
if (node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(node_path__WEBPACK_IMPORTED_MODULE_2__.join(scanRoot, 'pyproject.toml'))) return { cmd: 'pytest', args: [] };
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Produce a structured upgrade plan WITHOUT modifying anything on disk.
|
|
112
|
+
// Safe to call repeatedly; runs the ecosystem's --dry-run command.
|
|
113
|
+
async function planScaUpgrade({ scanRoot, finding }) {
|
|
114
|
+
if (!finding || finding.type !== 'vulnerable_dep') {
|
|
115
|
+
return { ok: false, reason: 'not a vulnerable_dep finding' };
|
|
116
|
+
}
|
|
117
|
+
const eco = ECOSYSTEM[finding.ecosystem];
|
|
118
|
+
if (!eco) {
|
|
119
|
+
return {
|
|
120
|
+
ok: true,
|
|
121
|
+
mode: 'manual',
|
|
122
|
+
reason: `ecosystem '${finding.ecosystem}' has no automated upgrade in v1`,
|
|
123
|
+
package: finding.name, currentVersion: finding.version,
|
|
124
|
+
targetVersion: (finding.fixedVersions && finding.fixedVersions[0]) || null,
|
|
125
|
+
command: null,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const target = (finding.fixedVersions && finding.fixedVersions[0]) || null;
|
|
129
|
+
if (!target) {
|
|
130
|
+
return { ok: false, reason: 'no fixed version in OSV record' };
|
|
131
|
+
}
|
|
132
|
+
const isBreaking = (_majorVersion(target) ?? 0) > (_majorVersion(finding.version) ?? 0);
|
|
133
|
+
const apply = eco.apply(finding.name, target);
|
|
134
|
+
const dryRunSpec = eco.dryRun(finding.name, target);
|
|
135
|
+
let peerDeps = [], transitiveImpact = 0, rawSummary = null, dryRunOk = null;
|
|
136
|
+
if (dryRunSpec) {
|
|
137
|
+
try {
|
|
138
|
+
const r = await execFileAsync(dryRunSpec.cmd, dryRunSpec.args, {
|
|
139
|
+
cwd: scanRoot, timeout: 60_000, maxBuffer: 8 * 1024 * 1024,
|
|
140
|
+
});
|
|
141
|
+
const parsed = eco.parseDryRun(r.stdout);
|
|
142
|
+
peerDeps = parsed.peerDeps;
|
|
143
|
+
transitiveImpact = parsed.transitiveImpact;
|
|
144
|
+
rawSummary = parsed.rawSummary;
|
|
145
|
+
dryRunOk = true;
|
|
146
|
+
} catch (e) {
|
|
147
|
+
// Dry-run failed (e.g. peer-dep resolution conflict). Surface the
|
|
148
|
+
// error structurally so the caller can decide whether to proceed.
|
|
149
|
+
dryRunOk = false;
|
|
150
|
+
const stderr = (e && e.stderr) || (e && e.message) || '';
|
|
151
|
+
peerDeps = /peer dep/i.test(stderr) ? [String(stderr).slice(0, 500)] : [];
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
ok: true,
|
|
156
|
+
mode: 'auto',
|
|
157
|
+
ecosystem: finding.ecosystem,
|
|
158
|
+
package: finding.name,
|
|
159
|
+
currentVersion: finding.version,
|
|
160
|
+
targetVersion: target,
|
|
161
|
+
isBreaking,
|
|
162
|
+
command: `${apply.cmd} ${apply.args.join(' ')}`,
|
|
163
|
+
manifestFiles: eco.manifests,
|
|
164
|
+
dryRun: { ok: dryRunOk, command: dryRunSpec ? `${dryRunSpec.cmd} ${dryRunSpec.args.join(' ')}` : null, peerDeps, transitiveImpact, rawSummary },
|
|
165
|
+
testCommand: (() => { const t = _detectTestCommand(scanRoot); return t ? `${t.cmd} ${t.args.join(' ')}` : null; })(),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Apply the upgrade. Backs up affected manifests, runs the install, runs
|
|
170
|
+
// the project's test command if detected, and ROLLS BACK on failure.
|
|
171
|
+
// Audit-logged via the MCP audit layer at the call site.
|
|
172
|
+
async function applyScaUpgrade({ scanRoot, finding, runTests = true }) {
|
|
173
|
+
const plan = await planScaUpgrade({ scanRoot, finding });
|
|
174
|
+
if (!plan.ok) return { applied: false, reason: plan.reason };
|
|
175
|
+
if (plan.mode === 'manual') {
|
|
176
|
+
return { applied: false, reason: plan.reason, plan };
|
|
177
|
+
}
|
|
178
|
+
const eco = ECOSYSTEM[finding.ecosystem];
|
|
179
|
+
const target = plan.targetVersion;
|
|
180
|
+
|
|
181
|
+
// Backup pass — record original contents of every relevant manifest so
|
|
182
|
+
// we can restore on test failure. node_modules / vendor dirs are NOT
|
|
183
|
+
// backed up (too big); they'll be rebuilt by re-running the install on
|
|
184
|
+
// restore.
|
|
185
|
+
const stateDir = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_6__/* .statePath */ .BQ)(scanRoot, 'sca-upgrade-history');
|
|
186
|
+
node_fs__WEBPACK_IMPORTED_MODULE_0__.mkdirSync(stateDir, { recursive: true });
|
|
187
|
+
const upgradeId = node_crypto__WEBPACK_IMPORTED_MODULE_3__.randomBytes(8).toString('hex');
|
|
188
|
+
const backups = {};
|
|
189
|
+
for (const mf of eco.manifests) {
|
|
190
|
+
const abs = node_path__WEBPACK_IMPORTED_MODULE_2__.join(scanRoot, mf);
|
|
191
|
+
if (!node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(abs)) continue;
|
|
192
|
+
const content = await node_fs_promises__WEBPACK_IMPORTED_MODULE_1__.readFile(abs, 'utf8');
|
|
193
|
+
const backupPath = node_path__WEBPACK_IMPORTED_MODULE_2__.join(stateDir, `${upgradeId}-${mf.replace(/[\/\\]/g, '_')}.bak`);
|
|
194
|
+
await node_fs_promises__WEBPACK_IMPORTED_MODULE_1__.writeFile(backupPath, content);
|
|
195
|
+
backups[mf] = { abs, backupPath };
|
|
196
|
+
}
|
|
197
|
+
if (!Object.keys(backups).length) {
|
|
198
|
+
return { applied: false, reason: `no ${finding.ecosystem} manifest files found in scan root` };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Run the install.
|
|
202
|
+
const apply = eco.apply(finding.name, target);
|
|
203
|
+
let installOutput = '';
|
|
204
|
+
try {
|
|
205
|
+
const r = await execFileAsync(apply.cmd, apply.args, {
|
|
206
|
+
cwd: scanRoot, timeout: 300_000, maxBuffer: 16 * 1024 * 1024,
|
|
207
|
+
});
|
|
208
|
+
installOutput = (r.stdout || '') + (r.stderr || '');
|
|
209
|
+
} catch (e) {
|
|
210
|
+
// Install failed; restore backups (manifests may have been touched).
|
|
211
|
+
for (const { abs, backupPath } of Object.values(backups)) {
|
|
212
|
+
try { await node_fs_promises__WEBPACK_IMPORTED_MODULE_1__.copyFile(backupPath, abs); } catch {}
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
applied: false,
|
|
216
|
+
reason: `install failed: ${(e && e.message) || e}`.slice(0, 600),
|
|
217
|
+
installOutput: ((e && e.stdout) || '').slice(0, 1500),
|
|
218
|
+
restored: true,
|
|
219
|
+
upgradeId,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Optionally run the project's test command. On failure, restore.
|
|
224
|
+
let testResult = null;
|
|
225
|
+
if (runTests) {
|
|
226
|
+
const test = _detectTestCommand(scanRoot);
|
|
227
|
+
if (test) {
|
|
228
|
+
try {
|
|
229
|
+
const r = await execFileAsync(test.cmd, test.args, {
|
|
230
|
+
cwd: scanRoot, timeout: 600_000, maxBuffer: 16 * 1024 * 1024,
|
|
231
|
+
});
|
|
232
|
+
testResult = { ok: true, command: `${test.cmd} ${test.args.join(' ')}`, output: ((r.stdout || '') + (r.stderr || '')).slice(0, 2000) };
|
|
233
|
+
} catch (e) {
|
|
234
|
+
// Tests failed — restore manifests so the working tree is clean.
|
|
235
|
+
for (const { abs, backupPath } of Object.values(backups)) {
|
|
236
|
+
try { await node_fs_promises__WEBPACK_IMPORTED_MODULE_1__.copyFile(backupPath, abs); } catch {}
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
applied: false,
|
|
240
|
+
reason: `tests failed after upgrade: ${(e && e.message) || e}`.slice(0, 300),
|
|
241
|
+
testOutput: ((e && e.stdout) || '').slice(0, 2000),
|
|
242
|
+
restored: true,
|
|
243
|
+
upgradeId,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Success path — write a log entry so the user can audit / undo.
|
|
250
|
+
const logEntry = {
|
|
251
|
+
id: upgradeId,
|
|
252
|
+
timestamp: new Date().toISOString(),
|
|
253
|
+
ecosystem: finding.ecosystem,
|
|
254
|
+
package: finding.name,
|
|
255
|
+
from: finding.version,
|
|
256
|
+
to: target,
|
|
257
|
+
backups: Object.fromEntries(Object.entries(backups).map(([k, v]) => [k, v.backupPath])),
|
|
258
|
+
testResult,
|
|
259
|
+
isBreaking: plan.isBreaking,
|
|
260
|
+
finding: { id: finding.id, osvId: finding.osvId, cveAliases: finding.cveAliases },
|
|
261
|
+
};
|
|
262
|
+
const logFp = node_path__WEBPACK_IMPORTED_MODULE_2__.join(stateDir, 'log.json');
|
|
263
|
+
let log = [];
|
|
264
|
+
try { log = JSON.parse(node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(logFp, 'utf8')); } catch {}
|
|
265
|
+
log.push(logEntry);
|
|
266
|
+
node_fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync(logFp, JSON.stringify(log, null, 2));
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
applied: true,
|
|
270
|
+
upgradeId,
|
|
271
|
+
package: finding.name,
|
|
272
|
+
from: finding.version,
|
|
273
|
+
to: target,
|
|
274
|
+
isBreaking: plan.isBreaking,
|
|
275
|
+
testResult,
|
|
276
|
+
installOutput: installOutput.slice(0, 1500),
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
/***/ })
|
|
282
|
+
|
|
283
|
+
};
|
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__(
|
|
11
|
+
/* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2271);
|
|
12
12
|
// Secrets submodule view of the engine — credential + entropy + TODO scanning.
|
|
13
13
|
|
|
14
14
|
|
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__(
|
|
13
|
+
/* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2271);
|
|
14
14
|
// Shadowscan / security-DELTA on PR (v0.72).
|
|
15
15
|
//
|
|
16
16
|
// Most SAST PR-comment integrations show absolute counts — "12 findings
|
package/dist/838.index.js
CHANGED
|
@@ -14,7 +14,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
14
14
|
/* harmony import */ var node_child_process__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1421);
|
|
15
15
|
/* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3024);
|
|
16
16
|
/* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6760);
|
|
17
|
-
/* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(
|
|
17
|
+
/* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(2271);
|
|
18
18
|
// Closed-loop /fix verification (Sentinel-parity FR-L4-4, FR-L4-5).
|
|
19
19
|
//
|
|
20
20
|
// Given a candidate patch (the new file content + the finding stableId being
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
export const id = 839;
|
|
2
|
+
export const ids = [839];
|
|
3
|
+
export const modules = {
|
|
4
|
+
|
|
5
|
+
/***/ 3839:
|
|
6
|
+
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
|
7
|
+
|
|
8
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
9
|
+
/* harmony export */ queryFindingsMemory: () => (/* binding */ queryFindingsMemory)
|
|
10
|
+
/* harmony export */ });
|
|
11
|
+
/* unused harmony export _internals */
|
|
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
|
+
// Findings memory — natural-language Q&A over the institutional knowledge
|
|
15
|
+
// the scanner has accumulated. Backs the MCP query_findings_memory tool.
|
|
16
|
+
//
|
|
17
|
+
// Sources searched, in this order:
|
|
18
|
+
//
|
|
19
|
+
// 1. .agentic-security/last-scan.json current findings
|
|
20
|
+
// 2. .agentic-security/triage-memory.jsonl past wont-fix / FP decisions
|
|
21
|
+
// 3. .agentic-security/scan-history/*.json prior scans
|
|
22
|
+
// 4. .agentic-security/AGENTS.md continual-learning narrative
|
|
23
|
+
//
|
|
24
|
+
// Naive keyword matching for v1. Each match has a `score` (count of query
|
|
25
|
+
// terms matched) and a `source` ('finding' | 'triage' | 'history' |
|
|
26
|
+
// 'agents-md'). Returns top-10 by score.
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
const STATE = '.agentic-security';
|
|
32
|
+
|
|
33
|
+
function _read(scanRoot, name) {
|
|
34
|
+
try { return node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(node_path__WEBPACK_IMPORTED_MODULE_1__.join(scanRoot, STATE, name), 'utf8'); } catch { return null; }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function _readJson(scanRoot, name) {
|
|
38
|
+
const raw = _read(scanRoot, name);
|
|
39
|
+
if (!raw) return null;
|
|
40
|
+
try { return JSON.parse(raw); } catch { return null; }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function _terms(query) {
|
|
44
|
+
return String(query || '').toLowerCase().split(/\s+/).filter(t => t.length >= 2);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function _score(haystack, terms) {
|
|
48
|
+
const lower = String(haystack || '').toLowerCase();
|
|
49
|
+
let s = 0;
|
|
50
|
+
for (const t of terms) if (lower.includes(t)) s++;
|
|
51
|
+
return s;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function _findingHaystack(f) {
|
|
55
|
+
return [f.vuln, f.family, f.file, f.severity, f.description, f.cwe, f.id]
|
|
56
|
+
.filter(Boolean).join(' | ');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function _truncate(s, n = 160) {
|
|
60
|
+
return String(s || '').replace(/\s+/g, ' ').slice(0, n);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Run a natural-language query over the scanner's accumulated memory.
|
|
65
|
+
*/
|
|
66
|
+
function queryFindingsMemory(scanRoot, query) {
|
|
67
|
+
const terms = _terms(query);
|
|
68
|
+
if (!terms.length) return { results: [], count: 0 };
|
|
69
|
+
|
|
70
|
+
const results = [];
|
|
71
|
+
|
|
72
|
+
// 1. Current findings.
|
|
73
|
+
const scan = _readJson(scanRoot, 'last-scan.json');
|
|
74
|
+
if (scan && Array.isArray(scan.findings)) {
|
|
75
|
+
for (const f of scan.findings) {
|
|
76
|
+
const hay = _findingHaystack(f);
|
|
77
|
+
const score = _score(hay, terms);
|
|
78
|
+
if (!score) continue;
|
|
79
|
+
results.push({
|
|
80
|
+
source: 'finding',
|
|
81
|
+
score,
|
|
82
|
+
finding_id: f.id || null,
|
|
83
|
+
severity: f.severity,
|
|
84
|
+
family: f.family,
|
|
85
|
+
file: f.file,
|
|
86
|
+
line: f.line,
|
|
87
|
+
snippet: _truncate(f.vuln || f.description || f.family),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 2. Triage memory (past decisions).
|
|
93
|
+
const triageRaw = _read(scanRoot, 'triage-memory.jsonl');
|
|
94
|
+
if (triageRaw) {
|
|
95
|
+
const lines = triageRaw.split('\n').filter(Boolean);
|
|
96
|
+
for (const ln of lines) {
|
|
97
|
+
let entry; try { entry = JSON.parse(ln); } catch { continue; }
|
|
98
|
+
const hay = [entry.decision, entry.reason, entry.family, entry.vuln, entry.file].join(' ');
|
|
99
|
+
const score = _score(hay, terms);
|
|
100
|
+
if (!score) continue;
|
|
101
|
+
results.push({
|
|
102
|
+
source: 'triage',
|
|
103
|
+
score,
|
|
104
|
+
decision: entry.decision,
|
|
105
|
+
at: entry.at,
|
|
106
|
+
family: entry.family,
|
|
107
|
+
snippet: _truncate(entry.reason || entry.vuln),
|
|
108
|
+
bucket: entry.bucket,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 3. Scan history.
|
|
114
|
+
try {
|
|
115
|
+
const histDir = node_path__WEBPACK_IMPORTED_MODULE_1__.join(scanRoot, STATE, 'scan-history');
|
|
116
|
+
if (node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(histDir)) {
|
|
117
|
+
const files = node_fs__WEBPACK_IMPORTED_MODULE_0__.readdirSync(histDir).filter(f => f.endsWith('.json')).slice(-10);
|
|
118
|
+
for (const f of files) {
|
|
119
|
+
try {
|
|
120
|
+
const hist = JSON.parse(node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(node_path__WEBPACK_IMPORTED_MODULE_1__.join(histDir, f), 'utf8'));
|
|
121
|
+
if (!Array.isArray(hist.findings)) continue;
|
|
122
|
+
for (const x of hist.findings.slice(0, 50)) {
|
|
123
|
+
const hay = _findingHaystack(x);
|
|
124
|
+
const score = _score(hay, terms);
|
|
125
|
+
if (!score) continue;
|
|
126
|
+
results.push({
|
|
127
|
+
source: 'history',
|
|
128
|
+
score,
|
|
129
|
+
from: f.replace(/\.json$/, ''),
|
|
130
|
+
severity: x.severity,
|
|
131
|
+
family: x.family,
|
|
132
|
+
file: x.file,
|
|
133
|
+
snippet: _truncate(x.vuln || x.description),
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
} catch {}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
} catch {}
|
|
140
|
+
|
|
141
|
+
// 4. AGENTS.md narrative.
|
|
142
|
+
const agents = _read(scanRoot, 'AGENTS.md');
|
|
143
|
+
if (agents) {
|
|
144
|
+
const sections = agents.split(/^##\s+/m);
|
|
145
|
+
for (const sec of sections) {
|
|
146
|
+
const score = _score(sec, terms);
|
|
147
|
+
if (!score) continue;
|
|
148
|
+
const title = sec.split('\n')[0] || '';
|
|
149
|
+
results.push({
|
|
150
|
+
source: 'agents-md',
|
|
151
|
+
score,
|
|
152
|
+
title: _truncate(title, 80),
|
|
153
|
+
snippet: _truncate(sec.replace(title, ''), 200),
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Top-10 by score, ties broken by source priority (finding > triage >
|
|
159
|
+
// history > agents-md so live data wins).
|
|
160
|
+
const PRI = { finding: 4, triage: 3, history: 2, 'agents-md': 1 };
|
|
161
|
+
results.sort((a, b) => (b.score - a.score) || (PRI[b.source] - PRI[a.source]));
|
|
162
|
+
return { results: results.slice(0, 10), count: results.length };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const _internals = { _terms, _score, _findingHaystack };
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
/***/ })
|
|
169
|
+
|
|
170
|
+
};
|
package/dist/985.index.js
CHANGED
|
@@ -613,6 +613,8 @@ function _findById(scan, id) {
|
|
|
613
613
|
if (!scan) return null;
|
|
614
614
|
return (scan.findings || []).find(f => f.id === id)
|
|
615
615
|
|| (scan.secrets || []).find(f => f.id === id)
|
|
616
|
+
|| (scan.supplyChain || []).find(f => f.id === id)
|
|
617
|
+
|| (scan.logicVulns || []).find(f => f.id === id)
|
|
616
618
|
|| null;
|
|
617
619
|
}
|
|
618
620
|
|
|
@@ -843,6 +845,19 @@ const explain_finding = {
|
|
|
843
845
|
confidence: f.confidence ?? null,
|
|
844
846
|
hasReplacementFix: typeof f.fix?.replacement === 'string',
|
|
845
847
|
integrity: status,
|
|
848
|
+
// Risk-signal passthrough so agents can decide priority without
|
|
849
|
+
// re-reading last-scan.json or re-fetching OSV/KEV/EPSS. compositeRisk
|
|
850
|
+
// is the canonical sort key; the other fields are its provenance.
|
|
851
|
+
compositeRisk: f.compositeRisk ?? null,
|
|
852
|
+
compositeRiskTier: f.compositeRiskTier ?? null,
|
|
853
|
+
compositeRiskFactors: Array.isArray(f.compositeRiskFactors) ? f.compositeRiskFactors : [],
|
|
854
|
+
exploitability: f.exploitability ?? null,
|
|
855
|
+
exploitabilityTier: f.exploitabilityTier ?? null,
|
|
856
|
+
mitigationVerdict: f.mitigationVerdict ?? null,
|
|
857
|
+
kev: !!(f.kev || f.kevListed || f.weaponized),
|
|
858
|
+
epssScore: typeof f.epssScore === 'number' ? f.epssScore : null,
|
|
859
|
+
epssPercentile: typeof f.epssPercentile === 'number' ? f.epssPercentile : null,
|
|
860
|
+
exploitedNow: !!f.exploitedNow,
|
|
846
861
|
};
|
|
847
862
|
},
|
|
848
863
|
};
|
|
@@ -1306,6 +1321,56 @@ const read_agents_memory = {
|
|
|
1306
1321
|
},
|
|
1307
1322
|
};
|
|
1308
1323
|
|
|
1324
|
+
// ─── query_triage_memory ───────────────────────────────────────────────────
|
|
1325
|
+
// Natural-language Q&A over past triage decisions (wont-fix / false-positive
|
|
1326
|
+
// markings + reasons). Backed by .agentic-security/triage-memory.jsonl, which
|
|
1327
|
+
// is auto-populated by triage.transition(). Returns at most 10 most-relevant
|
|
1328
|
+
// past decisions.
|
|
1329
|
+
|
|
1330
|
+
const query_triage_memory = {
|
|
1331
|
+
name: 'query_triage_memory',
|
|
1332
|
+
description: 'Search past triage decisions (wont-fix / false-positive) by natural-language query. Returns up to 10 most-relevant past decisions with their reasons. Use when you see a new finding and want to know "did we already decide on something like this?" — answers in seconds without re-reading the full AGENTS.md narrative.',
|
|
1333
|
+
inputSchema: {
|
|
1334
|
+
type: 'object',
|
|
1335
|
+
additionalProperties: false,
|
|
1336
|
+
properties: {
|
|
1337
|
+
query: { type: 'string', description: 'Free-text terms to match against past reasons / vuln text / file paths / family names.' },
|
|
1338
|
+
},
|
|
1339
|
+
},
|
|
1340
|
+
async handler({ query }, ctx) {
|
|
1341
|
+
const { queryMemory } = await Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 1905));
|
|
1342
|
+
const results = queryMemory(ctx.sessionRoot, query || '');
|
|
1343
|
+
return {
|
|
1344
|
+
_meta: META,
|
|
1345
|
+
count: results.length,
|
|
1346
|
+
results,
|
|
1347
|
+
};
|
|
1348
|
+
},
|
|
1349
|
+
};
|
|
1350
|
+
|
|
1351
|
+
// ─── query_findings_memory ─────────────────────────────────────────────────
|
|
1352
|
+
// Natural-language Q&A across the scanner's accumulated institutional
|
|
1353
|
+
// memory: current findings + past triage decisions + scan history +
|
|
1354
|
+
// AGENTS.md narrative. Use to answer "have we seen something like this
|
|
1355
|
+
// before?" without reading multiple files.
|
|
1356
|
+
|
|
1357
|
+
const query_findings_memory = {
|
|
1358
|
+
name: 'query_findings_memory',
|
|
1359
|
+
description: 'Search the scanner accumulated memory (current scan findings + past wont-fix/false-positive decisions + scan history + AGENTS.md narrative) by natural-language terms. Returns top-10 results scored by term-match count and ranked finding > triage > history > AGENTS.md.',
|
|
1360
|
+
inputSchema: {
|
|
1361
|
+
type: 'object',
|
|
1362
|
+
additionalProperties: false,
|
|
1363
|
+
properties: {
|
|
1364
|
+
query: { type: 'string', description: 'Natural-language search terms (2+ chars each).' },
|
|
1365
|
+
},
|
|
1366
|
+
required: ['query'],
|
|
1367
|
+
},
|
|
1368
|
+
async handler({ query }, ctx) {
|
|
1369
|
+
const { queryFindingsMemory } = await __webpack_require__.e(/* import() */ 839).then(__webpack_require__.bind(__webpack_require__, 3839));
|
|
1370
|
+
return { _meta: META, ...queryFindingsMemory(ctx.sessionRoot, query || '') };
|
|
1371
|
+
},
|
|
1372
|
+
};
|
|
1373
|
+
|
|
1309
1374
|
// ─── lookup_cve ────────────────────────────────────────────────────────────
|
|
1310
1375
|
// LangChain harness-anatomy #8: bridge the knowledge-cutoff gap by exposing
|
|
1311
1376
|
// the local OSV / KEV / EPSS cache as a structured tool. Read-only — never
|
|
@@ -1327,7 +1392,81 @@ const lookup_cve = {
|
|
|
1327
1392
|
},
|
|
1328
1393
|
};
|
|
1329
1394
|
|
|
1330
|
-
|
|
1395
|
+
// ─── synthesize_sca_upgrade ───────────────────────────────────────────────
|
|
1396
|
+
// Phase 3 / Item 5 of the SCA improvement plan. Read-only counterpart to
|
|
1397
|
+
// apply_sca_upgrade — produces a structured upgrade plan via the
|
|
1398
|
+
// ecosystem's native --dry-run command. Safe to call any number of times.
|
|
1399
|
+
let _scaUpgrade;
|
|
1400
|
+
async function _getScaUpgrade() {
|
|
1401
|
+
if (!_scaUpgrade) _scaUpgrade = await __webpack_require__.e(/* import() */ 333).then(__webpack_require__.bind(__webpack_require__, 5333));
|
|
1402
|
+
return _scaUpgrade;
|
|
1403
|
+
}
|
|
1404
|
+
const synthesize_sca_upgrade = {
|
|
1405
|
+
name: 'synthesize_sca_upgrade',
|
|
1406
|
+
description: 'Generate an upgrade plan for a single SCA finding. Runs the ecosystem dry-run (npm install --dry-run, pip install --dry-run, cargo update --dry-run). Returns { ecosystem, package, currentVersion, targetVersion, isBreaking, command, manifestFiles, dryRun, testCommand }. No writes.',
|
|
1407
|
+
inputSchema: {
|
|
1408
|
+
type: 'object',
|
|
1409
|
+
additionalProperties: false,
|
|
1410
|
+
properties: {
|
|
1411
|
+
finding_id: { type: 'string', minLength: 1, maxLength: 256 },
|
|
1412
|
+
},
|
|
1413
|
+
required: ['finding_id'],
|
|
1414
|
+
},
|
|
1415
|
+
async handler({ finding_id }, ctx) {
|
|
1416
|
+
const { scan, status } = _readLastScanVerified(ctx.sessionRoot, { allowUnsigned: true });
|
|
1417
|
+
if (!scan) throw new Error(`No usable scan state (${status}).`);
|
|
1418
|
+
const f = _findById(scan, finding_id);
|
|
1419
|
+
if (!f) throw new Error(`Finding not found: ${finding_id}`);
|
|
1420
|
+
if (f.type !== 'vulnerable_dep') {
|
|
1421
|
+
return { _meta: META, ok: false, reason: 'finding is not an SCA vulnerable_dep — use synthesize_fix for SAST findings' };
|
|
1422
|
+
}
|
|
1423
|
+
const { planScaUpgrade } = await _getScaUpgrade();
|
|
1424
|
+
const plan = await planScaUpgrade({ scanRoot: ctx.sessionRoot, finding: f });
|
|
1425
|
+
return { _meta: META, ...plan };
|
|
1426
|
+
},
|
|
1427
|
+
};
|
|
1428
|
+
|
|
1429
|
+
// ─── apply_sca_upgrade ────────────────────────────────────────────────────
|
|
1430
|
+
// Phase 3 / Item 5 of the SCA improvement plan. The MCP `apply_fix` path
|
|
1431
|
+
// refuses every package-manager manifest by design. This tool bypasses
|
|
1432
|
+
// that ONLY for the install pathway — it shells out to the ecosystem's
|
|
1433
|
+
// native package manager (npm / pip / cargo / go) which is the right
|
|
1434
|
+
// surface for safely modifying manifests + lockfiles. Backs up affected
|
|
1435
|
+
// manifests before the install; runs the project's test command (if
|
|
1436
|
+
// detected); rolls back manifests if tests fail.
|
|
1437
|
+
const apply_sca_upgrade = {
|
|
1438
|
+
name: 'apply_sca_upgrade',
|
|
1439
|
+
description: 'Apply a vulnerable_dep upgrade. Backs up manifests, runs the package manager, runs the project test command, restores manifests on test failure. Requires confirm:true. Set run_tests:false to skip the test gate (NOT recommended).',
|
|
1440
|
+
inputSchema: {
|
|
1441
|
+
type: 'object',
|
|
1442
|
+
additionalProperties: false,
|
|
1443
|
+
properties: {
|
|
1444
|
+
finding_id: { type: 'string', minLength: 1, maxLength: 256 },
|
|
1445
|
+
confirm: { type: 'boolean' },
|
|
1446
|
+
run_tests: { type: 'boolean' },
|
|
1447
|
+
},
|
|
1448
|
+
required: ['finding_id', 'confirm'],
|
|
1449
|
+
},
|
|
1450
|
+
async handler({ finding_id, confirm, run_tests = true }, ctx) {
|
|
1451
|
+
if (confirm !== true) {
|
|
1452
|
+
return { _meta: META, applied: false, reason: 'apply_sca_upgrade requires confirm: true.' };
|
|
1453
|
+
}
|
|
1454
|
+
const { scan, status } = _readLastScanVerified(ctx.sessionRoot, { allowUnsigned: false });
|
|
1455
|
+
if (!scan) {
|
|
1456
|
+
return { _meta: META, applied: false, reason: `last-scan.json failed integrity check: ${status}. Run a fresh scan.` };
|
|
1457
|
+
}
|
|
1458
|
+
const f = _findById(scan, finding_id);
|
|
1459
|
+
if (!f) return { _meta: META, applied: false, reason: `Finding not found: ${finding_id}` };
|
|
1460
|
+
if (f.type !== 'vulnerable_dep') {
|
|
1461
|
+
return { _meta: META, applied: false, reason: 'finding is not an SCA vulnerable_dep — use apply_fix for SAST findings' };
|
|
1462
|
+
}
|
|
1463
|
+
const { applyScaUpgrade } = await _getScaUpgrade();
|
|
1464
|
+
const result = await applyScaUpgrade({ scanRoot: ctx.sessionRoot, finding: f, runTests: run_tests });
|
|
1465
|
+
return { _meta: META, ...result };
|
|
1466
|
+
},
|
|
1467
|
+
};
|
|
1468
|
+
|
|
1469
|
+
const ALL_TOOLS = [scan_diff, query_taint, explain_finding, apply_fix, verify_fix, synthesize_fix, find_rule_module, append_scratchpad, read_scratchpad, append_agents_memory, read_agents_memory, lookup_cve, synthesize_sca_upgrade, apply_sca_upgrade, query_triage_memory, query_findings_memory];
|
|
1331
1470
|
|
|
1332
1471
|
;// CONCATENATED MODULE: ./src/mcp/validate.js
|
|
1333
1472
|
// Minimal JSON Schema validator — just the subset our tool schemas use.
|