@clear-capabilities/agentic-security-scanner 0.172.0 → 0.173.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/bin/agentic-security.js +37 -0
- package/dist/119.index.js +197 -0
- package/dist/128.index.js +129 -0
- package/dist/1293.index.js +5689 -0
- package/dist/1336.index.js +101 -101
- package/dist/1351.index.js +133 -0
- package/dist/1387.index.js +271 -0
- package/dist/1437.index.js +23 -0
- package/dist/1445.index.js +110 -0
- package/dist/1505.index.js +151 -0
- package/dist/1557.index.js +293 -0
- package/dist/1621.index.js +311 -0
- package/dist/1751.index.js +160 -0
- package/dist/1765.index.js +727 -0
- package/dist/1861.index.js +690 -0
- package/dist/1898.index.js +116 -0
- package/dist/1906.index.js +433 -0
- package/dist/1920.index.js +1 -291
- package/dist/208.index.js +1631 -0
- package/dist/2199.index.js +0 -239
- package/dist/2223.index.js +159 -0
- package/dist/2530.index.js +152 -0
- package/dist/2565.index.js +116 -0
- package/dist/2574.index.js +127 -0
- package/dist/2620.index.js +345 -0
- package/dist/2773.index.js +98 -0
- package/dist/2775.index.js +32 -0
- package/dist/2966.index.js +171 -0
- package/dist/2972.index.js +87 -0
- package/dist/2991.index.js +131 -0
- package/dist/3063.index.js +833 -0
- package/dist/3075.index.js +227 -0
- package/dist/3121.index.js +297 -0
- package/dist/3314.index.js +263 -0
- package/dist/3480.index.js +1822 -0
- package/dist/3481.index.js +24 -0
- package/dist/3506.index.js +268 -0
- package/dist/3533.index.js +591 -0
- package/dist/3653.index.js +450 -0
- package/dist/3822.index.js +131 -0
- package/dist/3868.index.js +292 -0
- package/dist/3917.index.js +152 -0
- package/dist/3918.index.js +192 -0
- package/dist/4090.index.js +288 -0
- package/dist/4121.index.js +1190 -0
- package/dist/4142.index.js +0 -239
- package/dist/4336.index.js +436 -0
- package/dist/4387.index.js +532 -0
- package/dist/4413.index.js +302 -0
- package/dist/4476.index.js +4510 -0
- package/dist/4491.index.js +133 -0
- package/dist/4538.index.js +185 -0
- package/dist/4684.index.js +319 -0
- package/dist/4740.index.js +165 -0
- package/dist/4780.index.js +209 -0
- package/dist/4802.index.js +225 -0
- package/dist/4812.index.js +379 -0
- package/dist/4835.index.js +126 -0
- package/dist/494.index.js +226 -0
- package/dist/4991.index.js +549 -0
- package/dist/5199.index.js +305 -0
- package/dist/5216.index.js +109 -0
- package/dist/5439.index.js +301 -0
- package/dist/5443.index.js +97 -0
- package/dist/5496.index.js +377 -0
- package/dist/550.index.js +2 -1
- package/dist/5801.index.js +588 -0
- package/dist/5858.index.js +223 -0
- package/dist/5933.index.js +143 -0
- package/dist/601.index.js +23 -0
- package/dist/6173.index.js +273 -0
- package/dist/6291.index.js +1871 -0
- package/dist/6312.index.js +666 -0
- package/dist/634.index.js +16182 -0
- package/dist/644.index.js +87 -0
- package/dist/6508.index.js +283 -0
- package/dist/6560.index.js +645 -0
- package/dist/6576.index.js +422 -0
- package/dist/6579.index.js +271 -0
- package/dist/6745.index.js +305 -0
- package/dist/6818.index.js +21642 -0
- package/dist/6972.index.js +167 -0
- package/dist/6995.index.js +163 -0
- package/dist/7249.index.js +191 -0
- package/dist/7372.index.js +1197 -0
- package/dist/7405.index.js +23 -6
- package/dist/7546.index.js +541 -0
- package/dist/76.index.js +448 -0
- package/dist/7879.index.js +163 -0
- package/dist/8025.index.js +130 -0
- package/dist/8054.index.js +248 -0
- package/dist/8097.index.js +201 -0
- package/dist/8126.index.js +191 -0
- package/dist/8178.index.js +78 -0
- package/dist/818.index.js +212 -0
- package/dist/8382.index.js +93 -0
- package/dist/8408.index.js +261 -0
- package/dist/8540.index.js +174 -0
- package/dist/8635.index.js +802 -0
- package/dist/8688.index.js +410 -0
- package/dist/8699.index.js +779 -0
- package/dist/8891.index.js +457 -0
- package/dist/8916.index.js +885 -0
- package/dist/9146.index.js +170 -0
- package/dist/9168.index.js +841 -0
- package/dist/9292.index.js +369 -0
- package/dist/9382.index.js +338 -48
- package/dist/9422.index.js +866 -0
- package/dist/9440.index.js +453 -0
- package/dist/9479.index.js +152 -0
- package/dist/9481.index.js +220 -0
- package/dist/9539.index.js +100 -0
- package/dist/9547.index.js +103 -0
- package/dist/9560.index.js +41 -61
- package/dist/9692.index.js +90 -0
- package/dist/9800.index.js +213 -1
- package/dist/9899.index.js +138 -0
- package/dist/9981.index.js +206 -0
- package/dist/999.index.js +117 -0
- package/dist/agentic-security.mjs +28 -28
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/package.json +31 -6
- package/src/context-intelligence/CLAUDE.md +4 -0
- package/src/context-intelligence/adapters/haskell-hie.js +28 -1
- package/src/context-intelligence/candidates.js +2 -1
- package/src/context-intelligence/canonical.js +25 -0
- package/src/context-intelligence/capabilities.js +2 -1
- package/src/context-intelligence/cli.js +16 -10
- package/src/context-intelligence/config.js +4 -1
- package/src/context-intelligence/controls.js +108 -10
- package/src/context-intelligence/counterexample.js +200 -0
- package/src/context-intelligence/cross-route.js +111 -0
- package/src/context-intelligence/entrypoints.js +2 -2
- package/src/context-intelligence/evaluation/index.js +11 -4
- package/src/context-intelligence/evaluation/scripted-reader.js +12 -2
- package/src/context-intelligence/findings.js +166 -0
- package/src/context-intelligence/invariants.js +197 -0
- package/src/context-intelligence/queue.js +6 -1
- package/src/context-intelligence/reachability.js +10 -4
- package/src/context-intelligence/review.js +129 -16
- package/src/dataflow/access-paths.js +11 -0
- package/src/dataflow/analysis-limits.js +66 -0
- package/src/dataflow/engine.js +272 -20
- package/src/dataflow/service-edges.js +174 -0
- package/src/dataflow/summaries.js +7 -1
- package/src/discovery/judge.js +2 -1
- package/src/engine.js +82 -14
- package/src/fix/apply-fix-service.js +55 -8
- package/src/fix/batch-journal.js +84 -0
- package/src/fix/exploit-replay.js +51 -0
- package/src/fix/footprint.js +65 -0
- package/src/fix/patch-policy.js +49 -0
- package/src/fix/secret-persistence.js +39 -0
- package/src/fix/test-commands.js +84 -0
- package/src/fix/verification-adapters.js +97 -0
- package/src/fix/verification-record.js +243 -0
- package/src/fix/verify-patch.js +89 -0
- package/src/ir/CLAUDE.md +5 -0
- package/src/ir/callgraph.js +23 -1
- package/src/ir/module-resolver.js +315 -0
- package/src/ir/parser-js.js +21 -2
- package/src/ir/semantic-graph.js +245 -0
- package/src/lineage/build-attested-correlation.js +63 -0
- package/src/lineage/deployment-identity.js +76 -0
- package/src/mcp/tools.js +13 -34
- package/src/pipeline/canonical-output.js +64 -0
- package/src/pipeline/detector-runner.js +7 -0
- package/src/pipeline/scan-health.js +14 -0
- package/src/pipeline/supervised-analyzers.js +125 -0
- package/src/pipeline/supervised-child.js +17 -0
- package/src/pipeline/telemetry.js +79 -0
- package/src/posture/accuracy-scorecard.js +98 -9
- package/src/posture/artifact-registry.js +5 -0
- package/src/posture/assurance/evidence-registry.js +215 -0
- package/src/posture/assurance/release-package-verify.js +135 -0
- package/src/posture/assurance/release-package.js +118 -0
- package/src/posture/assurance/release-verdict.js +94 -0
- package/src/posture/assurance/waiver.js +200 -0
- package/src/posture/compliance-truth.js +100 -0
- package/src/posture/evaluation/scan-child.js +3 -1
- package/src/posture/evaluation/why-missed.js +3 -1
- package/src/posture/exploitability-evidence.js +141 -0
- package/src/posture/fix-verify.js +4 -2
- package/src/posture/fleet/exposure-ranking.js +115 -0
- package/src/posture/fleet/service-chain-graph.js +150 -0
- package/src/posture/graph-reconciliation.js +154 -0
- package/src/posture/provenance/coverage.js +103 -0
- package/src/posture/provenance/git-evidence.js +84 -0
- package/src/posture/provenance/origin-resolver.js +7 -1
- package/src/posture/provenance/predicate-replay.js +14 -4
- package/src/posture/runtime-correlation.js +61 -15
- package/src/posture/runtime-explanation.js +116 -0
- package/src/posture/runtime-trace-ingest.js +194 -0
- package/src/posture/scan-checkpoint.js +38 -10
- package/src/posture/waf-ingest.js +7 -2
- package/src/report/index.js +4 -0
- package/src/report/oscal.js +38 -0
- package/src/report/perspectives.js +150 -0
- package/src/sast/_comment-strip.js +8 -1
- package/src/sast/ruby.js +26 -0
- package/src/util/string-memo.js +38 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
export const id = 8408;
|
|
2
|
+
export const ids = [8408];
|
|
3
|
+
export const modules = {
|
|
4
|
+
|
|
5
|
+
/***/ 18408:
|
|
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 */ LANGUAGE_BOM_VERSION: () => (/* binding */ LANGUAGE_BOM_VERSION),
|
|
11
|
+
/* harmony export */ genericPurl: () => (/* binding */ genericPurl),
|
|
12
|
+
/* harmony export */ languageBom: () => (/* binding */ languageBom),
|
|
13
|
+
/* harmony export */ languagePbom: () => (/* binding */ languagePbom),
|
|
14
|
+
/* harmony export */ purlFromGitHost: () => (/* binding */ purlFromGitHost),
|
|
15
|
+
/* harmony export */ purlFromUpstreamUrl: () => (/* binding */ purlFromUpstreamUrl)
|
|
16
|
+
/* harmony export */ });
|
|
17
|
+
/* harmony import */ var _haskell_supply_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(67052);
|
|
18
|
+
/* harmony import */ var _haskell_sca_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(1426);
|
|
19
|
+
/* harmony import */ var _haskell_resolved_graph_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(61906);
|
|
20
|
+
/* harmony import */ var _nix_inventory_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(62656);
|
|
21
|
+
// Hackage and Nix components, dependency edges and target provenance for the SBOM emitters (X-010).
|
|
22
|
+
//
|
|
23
|
+
// Turns what the language analyses already know into BOM-shaped data WITHOUT inventing any of it:
|
|
24
|
+
//
|
|
25
|
+
// * A version appears only where a resolver or a lock states one. A declared range (`aeson ==2.1.*`) is a property,
|
|
26
|
+
// never a version. A Nix package selector (`pkgs.git`) is not a component at all.
|
|
27
|
+
// * A dependency graph appears only from a fresh resolved plan/export. A manifest or a freeze file gives declared
|
|
28
|
+
// edges at most, and the document says so through `compositions` (`incomplete`), never `complete`.
|
|
29
|
+
// * Resolved builds are never merged away: two Hackage units that differ in flags, or two Nix outputs that share a
|
|
30
|
+
// name and version but not a store hash, stay two components with two bom-refs.
|
|
31
|
+
// * A license appears only when a source states one. Nothing here guesses a license from a name.
|
|
32
|
+
// * Purls follow the registered type rules: `hackage`, `github` for validated upstream hosts, and `generic` (with
|
|
33
|
+
// vcs_url / download_url qualifiers) as the labelled fallback. Nix build provenance travels beside the identity
|
|
34
|
+
// as properties (store path, derivation, system, installable, lock digest), not inside the purl.
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
const LANGUAGE_BOM_VERSION = 'language-bom/1';
|
|
42
|
+
|
|
43
|
+
const enc = (s) => encodeURIComponent(String(s)).replace(/%2F/gi, '/').replace(/%40/g, '%40');
|
|
44
|
+
const qEnc = (s) => encodeURIComponent(String(s));
|
|
45
|
+
const prop = (name, value) => (value === null || value === undefined || value === '' ? null : { name: `agentic-security:${name}`, value: typeof value === 'string' ? value : JSON.stringify(value) });
|
|
46
|
+
const props = (...xs) => xs.filter(Boolean);
|
|
47
|
+
|
|
48
|
+
/** purl for a generic component, qualifiers sorted by key as the purl spec requires. */
|
|
49
|
+
function genericPurl(name, version, qualifiers = {}) {
|
|
50
|
+
const q = Object.entries(qualifiers).filter(([, v]) => v !== null && v !== undefined && v !== '').sort(([a], [b]) => (a < b ? -1 : 1)).map(([k, v]) => `${k.toLowerCase()}=${qEnc(v)}`);
|
|
51
|
+
return `pkg:generic/${enc(name)}${version ? `@${qEnc(version)}` : ''}${q.length ? `?${q.join('&')}` : ''}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** A purl for a recognised source host; null otherwise (never guessed). */
|
|
55
|
+
function purlFromGitHost(type, owner, repo, rev) {
|
|
56
|
+
if (!['github', 'gitlab', 'bitbucket'].includes(type) || !owner || !repo) return null;
|
|
57
|
+
return `pkg:${type}/${enc(String(owner).toLowerCase())}/${enc(String(repo).toLowerCase())}${rev ? `@${qEnc(rev)}` : ''}`;
|
|
58
|
+
}
|
|
59
|
+
const GH_URL = /^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?\/(?:releases\/download|archive)\//i;
|
|
60
|
+
/** Validated upstream identity from a source URL on a known host. */
|
|
61
|
+
function purlFromUpstreamUrl(url, version) {
|
|
62
|
+
const m = GH_URL.exec(String(url || ''));
|
|
63
|
+
return m ? purlFromGitHost('github', m[1], m[2], version) : null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const cdxScope = (scopes) => ((scopes || []).includes('runtime') ? 'required' : 'excluded');
|
|
67
|
+
|
|
68
|
+
// ── Haskell ─────────────────────────────────────────────────────────────────
|
|
69
|
+
function haskellBom(files, opts) {
|
|
70
|
+
const out = { components: [], dependencies: [], compositions: [], properties: [], gaps: [], root: null };
|
|
71
|
+
const { components: declared, manifests } = (0,_haskell_supply_js__WEBPACK_IMPORTED_MODULE_0__/* .hackageComponents */ .vG)(files);
|
|
72
|
+
if (!manifests) return out;
|
|
73
|
+
const pkg = (manifests.packages || [])[0] || null;
|
|
74
|
+
const rootRef = pkg ? `haskell-root:${pkg.name}@${pkg.version || 'unversioned'}` : 'haskell-root';
|
|
75
|
+
if (pkg) out.root = { type: 'application', 'bom-ref': rootRef, name: pkg.name, ...(pkg.version ? { version: pkg.version } : {}) };
|
|
76
|
+
|
|
77
|
+
const graph = opts.resolved || (0,_haskell_resolved_graph_js__WEBPACK_IMPORTED_MODULE_2__/* .buildResolvedGraph */ .xU)({ manifests, plan: opts.plan, stackExport: opts.stackExport, expected: opts.expected, rejectStale: opts.rejectStale });
|
|
78
|
+
out.properties.push(prop('haskell:resolution', graph.source), prop('haskell:freshness', graph.freshness && graph.freshness.status), prop('haskell:closure', graph.closure));
|
|
79
|
+
if (graph.compiler) out.properties.push(prop('haskell:compiler', graph.compiler.id));
|
|
80
|
+
out.gaps.push(...(graph.gaps || []).map((g) => ({ ...g, source: 'haskell' })));
|
|
81
|
+
|
|
82
|
+
const refOf = new Map(); // unit id -> bom-ref
|
|
83
|
+
if (graph.graphAvailable && graph.units) {
|
|
84
|
+
for (const u of graph.units) {
|
|
85
|
+
if (u.origin === 'local') { refOf.set(u.id, rootRef); continue; }
|
|
86
|
+
const ref = `hackage:${u.id}`;
|
|
87
|
+
refOf.set(u.id, ref);
|
|
88
|
+
let purl = null;
|
|
89
|
+
if (u.origin === 'hackage' || u.origin === 'compiler') purl = (0,_haskell_sca_js__WEBPACK_IMPORTED_MODULE_1__/* .hackagePurl */ .Gf)(u.name, u.version || null);
|
|
90
|
+
else if (u.origin === 'vcs') purl = genericPurl(u.name, u.version, { vcs_url: u.repo && u.repo.uri });
|
|
91
|
+
else if (u.origin === 'url') purl = genericPurl(u.name, u.version, { download_url: u.repo && u.repo.uri });
|
|
92
|
+
out.components.push({
|
|
93
|
+
ecosystem: 'hackage', name: u.name, version: u.version || null, purl, bomRef: ref, filePath: (files && Object.keys(files).find((f) => /\.cabal$|cabal\.project$/.test(f))) || undefined, cdxScope: cdxScope(u.scopes), scopes: u.scopes || [],
|
|
94
|
+
// identity across versions: the package, plus its flag assignment when it has one (two builds stay two components)
|
|
95
|
+
identityKey: `hackage:${u.name}${u.flags && Object.keys(u.flags).length ? `?${JSON.stringify(u.flags)}` : ''}${u.sublibrary ? `:${u.sublibrary}` : ''}`,
|
|
96
|
+
...(u.sha256 ? { hashes: [{ alg: 'SHA-256', content: u.sha256 }] } : {}),
|
|
97
|
+
properties: props(prop('haskell:unitId', u.id), prop('haskell:origin', u.origin), prop('haskell:scopes', u.scopes), prop('haskell:flags', u.flags && Object.keys(u.flags).length ? u.flags : null), prop('haskell:boot', u.boot ? true : null), prop('haskell:component', u.component && u.component.kind), prop('haskell:originEvidence', u.originEvidence)),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
const dep = new Map();
|
|
101
|
+
for (const e of graph.edges || []) {
|
|
102
|
+
const from = refOf.get(e.from); const to = refOf.get(e.to);
|
|
103
|
+
if (!from || !to || from === to) continue;
|
|
104
|
+
if (!dep.has(from)) dep.set(from, new Map());
|
|
105
|
+
const toUnit = (graph.units || []).find((x) => x.id === e.to);
|
|
106
|
+
dep.get(from).set(to, (toUnit && toUnit.scopes && toUnit.scopes.includes('runtime')) ? 'runtime' : ((toUnit && toUnit.scopes && toUnit.scopes.includes('test')) ? 'test' : 'build'));
|
|
107
|
+
}
|
|
108
|
+
for (const [ref, m] of [...dep.entries()].sort((a, b) => (a[0] < b[0] ? -1 : 1))) out.dependencies.push({ ref, dependsOn: [...m.keys()].sort(), kinds: Object.fromEntries(m) });
|
|
109
|
+
out.compositions.push({ aggregate: graph.closure && graph.closure.complete ? 'complete' : 'incomplete', dependencies: [...new Set([...refOf.values()])].sort() });
|
|
110
|
+
} else {
|
|
111
|
+
// Declared inventory only: names and (where a freeze or exact pin states one) versions. Not a closure.
|
|
112
|
+
const seen = new Map();
|
|
113
|
+
for (const d of declared) {
|
|
114
|
+
const key = `${d.name}@${d.version || ''}`;
|
|
115
|
+
const ref = d.version ? `hackage:${d.name}@${d.version}` : `hackage:${d.name}`;
|
|
116
|
+
if (seen.has(key)) { const c = seen.get(key); if (!c.scopes.includes(d.scope)) c.scopes.push(d.scope); c.cdxScope = cdxScope(c.scopes); continue; }
|
|
117
|
+
const c = {
|
|
118
|
+
ecosystem: 'hackage', name: d.name, version: d.version || null, purl: (0,_haskell_sca_js__WEBPACK_IMPORTED_MODULE_1__/* .hackagePurl */ .Gf)(d.name, d.version || null), bomRef: ref, filePath: d.manifest || undefined, scopes: [d.scope], cdxScope: cdxScope([d.scope]), identityKey: `hackage:${d.name}`,
|
|
119
|
+
properties: props(prop('haskell:declaredRange', d.declaredRange), prop('haskell:resolution', d.resolution), prop('haskell:boot', d.ghcComponent ? true : null), prop('haskell:manifest', d.manifest)),
|
|
120
|
+
};
|
|
121
|
+
seen.set(key, c); out.components.push(c);
|
|
122
|
+
}
|
|
123
|
+
if (declared.length) {
|
|
124
|
+
out.dependencies.push({ ref: rootRef, dependsOn: out.components.map((c) => c.bomRef).sort(), kinds: Object.fromEntries(out.components.map((c) => [c.bomRef, (c.scopes || []).includes('runtime') ? 'runtime' : 'build'])) });
|
|
125
|
+
out.compositions.push({ aggregate: 'incomplete', dependencies: [rootRef] });
|
|
126
|
+
}
|
|
127
|
+
out.gaps.push({ kind: 'declared-inventory-only', source: 'haskell', detail: 'no fresh plan or Stack export: components are the declared dependencies, versions only where a freeze or an exact pin states one; transitive packages are not listed' });
|
|
128
|
+
}
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ── Nix ─────────────────────────────────────────────────────────────────────
|
|
133
|
+
function nixInputBom(files) {
|
|
134
|
+
const out = { components: [], dependencies: [], compositions: [], properties: [], gaps: [], root: null };
|
|
135
|
+
const inv = (0,_nix_inventory_js__WEBPACK_IMPORTED_MODULE_3__/* .analyzeNixInputs */ .Tw)({ files });
|
|
136
|
+
out.gaps.push(...inv.gaps.map((g) => ({ ...g, source: 'nix' })));
|
|
137
|
+
const refOf = new Map();
|
|
138
|
+
for (const f of inv.flakes) {
|
|
139
|
+
const nodes = (f.graph && f.graph.nodes) || [];
|
|
140
|
+
for (const n of nodes) {
|
|
141
|
+
if (n.root || !n.reachable) continue;
|
|
142
|
+
const key = `${f.file}:${n.key}`;
|
|
143
|
+
const ref = `nix-input:${key}`;
|
|
144
|
+
refOf.set(`${f.file}|${n.key}`, ref);
|
|
145
|
+
const lk = n.locked || null;
|
|
146
|
+
const rev = lk && (lk.rev || null);
|
|
147
|
+
let purl = null; let url = null;
|
|
148
|
+
if (lk && lk.type) {
|
|
149
|
+
purl = purlFromGitHost(lk.type, lk.owner, lk.repo, rev);
|
|
150
|
+
if (!purl && lk.url) purl = genericPurl(n.name, rev, lk.type === 'tarball' ? { download_url: lk.url } : { vcs_url: lk.url });
|
|
151
|
+
if (lk.type === 'github') url = `https://github.com/${lk.owner}/${lk.repo}`;
|
|
152
|
+
else if (lk.url) url = lk.url;
|
|
153
|
+
}
|
|
154
|
+
if (!purl) purl = genericPurl(n.name, null);
|
|
155
|
+
out.components.push({
|
|
156
|
+
ecosystem: 'nix', name: n.name, version: rev, purl, bomRef: ref, filePath: f.lock && f.lock.file ? f.lock.file : f.file, cdxScope: 'required', scopes: ['source'], identityKey: `nix-input:${f.file}:${n.key}`,
|
|
157
|
+
...(url ? { externalReferences: [{ type: 'vcs', url }] } : {}),
|
|
158
|
+
properties: props(prop('nix:class', 'flake-input'), prop('nix:lockNode', n.key), prop('nix:flakeFile', f.file), prop('nix:fetchType', lk && lk.type), prop('nix:narHash', lk && lk.narHash), prop('nix:locked', lk ? true : false), prop('nix:purlBasis', purl.startsWith('pkg:generic') ? 'generic-fallback' : 'validated-upstream-host')),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
const edges = (f.graph && f.graph.edges) || [];
|
|
162
|
+
const by = new Map();
|
|
163
|
+
for (const e of edges) { const a = refOf.get(`${f.file}|${e.from}`); const b = e.to ? refOf.get(`${f.file}|${e.to}`) : null; if (a && b && a !== b) { if (!by.has(a)) by.set(a, new Set()); by.get(a).add(b); } }
|
|
164
|
+
for (const [ref, set] of [...by.entries()].sort((x, y) => (x[0] < y[0] ? -1 : 1))) out.dependencies.push({ ref, dependsOn: [...set].sort(), kinds: Object.fromEntries([...set].map((s) => [s, 'runtime'])) });
|
|
165
|
+
}
|
|
166
|
+
if (out.components.length) out.compositions.push({ aggregate: 'incomplete', dependencies: out.components.map((c) => c.bomRef).sort() });
|
|
167
|
+
out.gaps.push({ kind: 'inputs-are-not-a-closure', source: 'nix', detail: 'flake.lock lists source inputs; it is not the build or runtime closure, so the Nix inputs are reported as incomplete' });
|
|
168
|
+
return out;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function nixClosureBom(closure) {
|
|
172
|
+
const out = { components: [], dependencies: [], compositions: [], properties: [], gaps: [], root: null };
|
|
173
|
+
if (!closure || closure.status !== 'ok') { if (closure) out.gaps.push({ kind: 'closure-not-usable', source: 'nix', detail: `closure import status ${closure.status}` }); return out; }
|
|
174
|
+
const prov = (closure.provenance || [])[0] || {};
|
|
175
|
+
const target = prov.target || {};
|
|
176
|
+
out.properties.push(prop('nix:target.system', target.system), prop('nix:target.installable', target.installable), prop('nix:toolVersion', prov.toolVersion), prop('nix:exactRuntimeClosure', closure.claims && closure.claims.exactRuntimeClosure), prop('nix:exactBuildGraph', closure.claims && closure.claims.exactBuildGraph));
|
|
177
|
+
const refOf = new Map();
|
|
178
|
+
for (const n of closure.nodes) {
|
|
179
|
+
if (n.kind !== 'output' && n.kind !== 'source') continue;
|
|
180
|
+
if (!n.pname && !n.storeName) continue;
|
|
181
|
+
const name = n.pname || n.storeName;
|
|
182
|
+
const ref = `nix-store:${n.hash}-${n.storeName}`;
|
|
183
|
+
refOf.set(n.id, ref);
|
|
184
|
+
const urls = (n.upstream && n.upstream.urls) || [];
|
|
185
|
+
let purl = null; let basis = 'generic-fallback';
|
|
186
|
+
for (const u of urls) { const p = purlFromUpstreamUrl(u, n.version); if (p) { purl = p; basis = 'validated-upstream-host'; break; } }
|
|
187
|
+
if (!purl) purl = genericPurl(name, n.version || null);
|
|
188
|
+
const scopes = n.scopes || [];
|
|
189
|
+
out.components.push({
|
|
190
|
+
ecosystem: 'nix', name, version: n.version || null, purl, bomRef: ref, cdxScope: cdxScope(scopes), scopes,
|
|
191
|
+
identityKey: `nix:${name}:${n.system || ''}:${n.outputName || 'out'}${n.sameNameDifferentBuild ? `:${n.hash}` : ''}`,
|
|
192
|
+
properties: props(prop('nix:class', n.kind === 'source' ? 'source' : 'store-output'), prop('nix:storePath', n.id), prop('nix:system', n.system), prop('nix:output', n.outputName), prop('nix:scopes', scopes), prop('nix:versionAuthority', n.versionAuthority), prop('nix:identity', n.identity), prop('nix:purlBasis', basis), prop('nix:sameNameDifferentBuild', n.sameNameDifferentBuild ? true : null), prop('nix:patches', (n.patches || []).map((p) => p.name)), prop('nix:installable', target.installable)),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
const by = new Map();
|
|
196
|
+
for (const e of closure.edges) {
|
|
197
|
+
if (e.kind !== 'reference') continue;
|
|
198
|
+
const a = refOf.get(e.from); const b = refOf.get(e.to);
|
|
199
|
+
if (!a || !b || a === b) continue;
|
|
200
|
+
if (!by.has(a)) by.set(a, new Set()); by.get(a).add(b);
|
|
201
|
+
}
|
|
202
|
+
for (const [ref, set] of [...by.entries()].sort((x, y) => (x[0] < y[0] ? -1 : 1))) out.dependencies.push({ ref, dependsOn: [...set].sort(), kinds: Object.fromEntries([...set].map((s) => [s, 'runtime'])) });
|
|
203
|
+
const runtimeRefs = closure.nodes.filter((n) => n.kind === 'output' && (n.scopes || []).includes('runtime')).map((n) => refOf.get(n.id)).filter(Boolean).sort();
|
|
204
|
+
if (runtimeRefs.length) out.compositions.push({ aggregate: closure.claims && closure.claims.exactRuntimeClosure ? 'complete' : 'incomplete', dependencies: runtimeRefs });
|
|
205
|
+
return out;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* @param {Record<string,string>} files rel path -> content (sources and manifests)
|
|
210
|
+
* @param {{closure?: object, resolved?: object, plan?: object, stackExport?: object, expected?: object, rejectStale?: boolean}} [opts]
|
|
211
|
+
* `closure` is an importNixClosure() result (an explicit, provenance-carrying export); nothing is run or fetched.
|
|
212
|
+
*/
|
|
213
|
+
function languageBom(files, opts = {}) {
|
|
214
|
+
const parts = [haskellBom(files || {}, opts), nixInputBom(files || {}), nixClosureBom(opts.closure)];
|
|
215
|
+
const merged = { version: LANGUAGE_BOM_VERSION, components: [], dependencies: [], compositions: [], properties: [], gaps: [], root: null };
|
|
216
|
+
const byRef = new Map();
|
|
217
|
+
for (const p of parts) {
|
|
218
|
+
for (const c of p.components) { if (!byRef.has(c.bomRef)) { byRef.set(c.bomRef, c); merged.components.push(c); } }
|
|
219
|
+
for (const d of p.dependencies) merged.dependencies.push(d);
|
|
220
|
+
merged.compositions.push(...p.compositions);
|
|
221
|
+
merged.properties.push(...p.properties);
|
|
222
|
+
merged.gaps.push(...p.gaps);
|
|
223
|
+
if (!merged.root && p.root) merged.root = p.root;
|
|
224
|
+
}
|
|
225
|
+
// a dependency edge may only name refs that exist (the root ref is allowed)
|
|
226
|
+
const known = new Set([...byRef.keys(), ...(merged.root ? [merged.root['bom-ref']] : [])]);
|
|
227
|
+
merged.dependencies = merged.dependencies.map((d) => ({ ...d, dependsOn: d.dependsOn.filter((r) => known.has(r)) })).filter((d) => known.has(d.ref));
|
|
228
|
+
merged.components.sort((a, b) => (a.bomRef < b.bomRef ? -1 : 1));
|
|
229
|
+
return merged;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* The language half of a Pipeline Bill of Materials: what is fetched and how it is pinned before anything is built.
|
|
234
|
+
* Static facts from the manifests and lock files only; nothing is fetched or evaluated.
|
|
235
|
+
*/
|
|
236
|
+
function languagePbom(files) {
|
|
237
|
+
const out = { version: LANGUAGE_BOM_VERSION, haskell: null, nix: null };
|
|
238
|
+
const { manifests } = (0,_haskell_supply_js__WEBPACK_IMPORTED_MODULE_0__/* .hackageComponents */ .vG)(files || {});
|
|
239
|
+
if (manifests) {
|
|
240
|
+
out.haskell = {
|
|
241
|
+
packages: (manifests.packages || []).map((p) => ({ name: p.name, version: p.version || null, buildType: p.buildType || null, file: p.file })),
|
|
242
|
+
customSetup: (manifests.packages || []).some((p) => String(p.buildType || '').toLowerCase() === 'custom'),
|
|
243
|
+
sourceRepositories: (manifests.sourceRepositories || []).map((r) => ({ location: r.location, tag: r.tag || null, pinned: /^[0-9a-f]{40}$/i.test(r.tag || ''), file: r.file, line: r.line })),
|
|
244
|
+
compiler: (manifests.projects || []).map((p) => p.withCompiler).find(Boolean) || null,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
const inv = (0,_nix_inventory_js__WEBPACK_IMPORTED_MODULE_3__/* .analyzeNixInputs */ .Tw)({ files: files || {} });
|
|
248
|
+
if (inv.flakes.length || inv.legacy.length) {
|
|
249
|
+
out.nix = {
|
|
250
|
+
inputs: inv.flakes.flatMap((f) => (f.declared || []).map((d) => ({ flake: f.file, name: d.name, url: d.url || null, status: d.status, fetchType: d.lockedFetch ? d.lockedFetch.type : null, rev: d.lockedFetch ? d.lockedFetch.rev || null : null, narHash: d.lockedFetch ? d.lockedFetch.narHash || null : null, locked: !!d.locked }))),
|
|
251
|
+
legacy: inv.legacy.map((l) => ({ kind: l.kind, file: l.file, line: l.line || null, resolution: l.resolution || null })),
|
|
252
|
+
closure: { complete: false, reason: 'inputs and fetchers only; a build or runtime closure needs an explicit export' },
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
return out;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
/***/ })
|
|
260
|
+
|
|
261
|
+
};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
export const id = 8540;
|
|
2
|
+
export const ids = [8540];
|
|
3
|
+
export const modules = {
|
|
4
|
+
|
|
5
|
+
/***/ 18540:
|
|
6
|
+
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
|
7
|
+
|
|
8
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
9
|
+
/* harmony export */ verifyImageSignature: () => (/* binding */ verifyImageSignature),
|
|
10
|
+
/* harmony export */ verifyProvenanceAttestation: () => (/* binding */ verifyProvenanceAttestation)
|
|
11
|
+
/* harmony export */ });
|
|
12
|
+
/* unused harmony export SIGNATURE_STATES */
|
|
13
|
+
/* harmony import */ var node_crypto__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(77598);
|
|
14
|
+
// PRD 5 P0 §5 — Signature Verification. States are exactly the PRD's own
|
|
15
|
+
// four, deliberately different from `sca/sigstore-verify.js`'s own vocabulary
|
|
16
|
+
// (`verified/unverified/tampered/unknown`) so the two are never confused by
|
|
17
|
+
// shared words — see docs/implementation-status/source-to-artifact-provenance.md's
|
|
18
|
+
// naming-discipline section:
|
|
19
|
+
//
|
|
20
|
+
// verified — a real cryptographic check ran and the signature matches.
|
|
21
|
+
// invalid — a real cryptographic check ran and the signature does NOT match.
|
|
22
|
+
// missing — no signature/attestation material was supplied at all.
|
|
23
|
+
// unavailable — material was supplied but could not be cryptographically
|
|
24
|
+
// checked (no public key given, or the check itself could
|
|
25
|
+
// not run) — the PRD's "never treat missing tooling as
|
|
26
|
+
// verified" clause means this state, not `verified`, is what
|
|
27
|
+
// an incomplete check must report.
|
|
28
|
+
//
|
|
29
|
+
// Two real, independent mechanisms are implemented:
|
|
30
|
+
// 1. A plain detached signature over the image digest string, verified
|
|
31
|
+
// with an operator-supplied public key. This is this tool's OWN local
|
|
32
|
+
// convention, not cosign's "simple signing" JSON format — disclosed in
|
|
33
|
+
// the implementation-status doc, not silently passed off as cosign-
|
|
34
|
+
// compatible.
|
|
35
|
+
// 2. Real DSSE (Dead Simple Signing Envelope) verification for in-toto/SLSA
|
|
36
|
+
// provenance attestations — the actual encoding cosign/Sigstore use for
|
|
37
|
+
// attestations, so a real cosign-keyed attestation bundle DOES verify
|
|
38
|
+
// here given the matching public key. `dssePae` is the exact PAE
|
|
39
|
+
// (Pre-Authentication Encoding) from the DSSE spec.
|
|
40
|
+
//
|
|
41
|
+
// Keyless/Rekor-transparency-log verification is NOT implemented — disclosed
|
|
42
|
+
// as a gap, never faked. `sca/sigstore-verify.js`'s Rekor-query pattern is
|
|
43
|
+
// the template a future increment could extend this module with.
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
const SIGNATURE_STATES = Object.freeze(['verified', 'invalid', 'missing', 'unavailable']);
|
|
47
|
+
|
|
48
|
+
function verifyRaw(payload, signature, publicKeyPem) {
|
|
49
|
+
let publicKey;
|
|
50
|
+
try {
|
|
51
|
+
publicKey = node_crypto__WEBPACK_IMPORTED_MODULE_0__.createPublicKey(publicKeyPem);
|
|
52
|
+
} catch (err) {
|
|
53
|
+
return { ok: false, error: `public key is not parseable: ${err.message}` };
|
|
54
|
+
}
|
|
55
|
+
const keyType = publicKey.asymmetricKeyType;
|
|
56
|
+
const algorithm = (keyType === 'ed25519' || keyType === 'ed448') ? null : 'sha256';
|
|
57
|
+
try {
|
|
58
|
+
const ok = node_crypto__WEBPACK_IMPORTED_MODULE_0__.verify(algorithm, payload, publicKey, signature);
|
|
59
|
+
return { ok, keyType };
|
|
60
|
+
} catch (err) {
|
|
61
|
+
return { ok: false, error: `signature verification failed to run: ${err.message}` };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Verifies a detached signature over an image's digest string.
|
|
67
|
+
* `signature`/`publicKeyPem` are Buffer/PEM-string; either missing means
|
|
68
|
+
* `missing` (nothing was supplied to check).
|
|
69
|
+
*/
|
|
70
|
+
function verifyImageSignature({ digest, signature, publicKeyPem }) {
|
|
71
|
+
if (typeof digest !== 'string' || !digest.startsWith('sha256:')) {
|
|
72
|
+
return { state: 'unavailable', reason: 'no image digest available to verify a signature against' };
|
|
73
|
+
}
|
|
74
|
+
if (!signature || !publicKeyPem) {
|
|
75
|
+
return { state: 'missing', reason: 'no detached signature and public key were supplied for this image' };
|
|
76
|
+
}
|
|
77
|
+
const { ok, error, keyType } = verifyRaw(Buffer.from(digest, 'utf8'), signature, publicKeyPem);
|
|
78
|
+
if (error) return { state: 'invalid', reason: error };
|
|
79
|
+
return ok
|
|
80
|
+
? { state: 'verified', keyType }
|
|
81
|
+
: { state: 'invalid', reason: 'signature does not match the image digest under the supplied public key' };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// DSSE Pre-Authentication Encoding: "DSSEv1" SP LEN(type) SP type SP LEN(body) SP body.
|
|
85
|
+
function dssePae(payloadType, payloadBytes) {
|
|
86
|
+
const typeBytes = Buffer.from(payloadType, 'utf8');
|
|
87
|
+
return Buffer.concat([
|
|
88
|
+
Buffer.from('DSSEv1 ', 'ascii'),
|
|
89
|
+
Buffer.from(String(typeBytes.length), 'ascii'),
|
|
90
|
+
Buffer.from(' ', 'ascii'),
|
|
91
|
+
typeBytes,
|
|
92
|
+
Buffer.from(' ', 'ascii'),
|
|
93
|
+
Buffer.from(String(payloadBytes.length), 'ascii'),
|
|
94
|
+
Buffer.from(' ', 'ascii'),
|
|
95
|
+
payloadBytes,
|
|
96
|
+
]);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const SLSA_PREDICATE_PREFIX = 'https://slsa.dev/provenance/';
|
|
100
|
+
const INTOTO_STATEMENT_TYPES = new Set(['https://in-toto.io/Statement/v0.1', 'https://in-toto.io/Statement/v1']);
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Verifies an in-toto/SLSA provenance attestation for an image.
|
|
104
|
+
* `envelope` is a DSSE envelope: `{ payloadType, payload (base64 in-toto
|
|
105
|
+
* Statement JSON), signatures: [{ sig (base64) }] }` — the real shape
|
|
106
|
+
* cosign/Sigstore attestations use. `publicKeyPem` verifies one of
|
|
107
|
+
* `signatures` against `dssePae(payloadType, payload)`.
|
|
108
|
+
*
|
|
109
|
+
* The subject-digest match is checked FIRST and independently of signature
|
|
110
|
+
* verification, so a mismatched subject is always `invalid` (never
|
|
111
|
+
* "unavailable", which would understate a real, checkable problem).
|
|
112
|
+
*/
|
|
113
|
+
function verifyProvenanceAttestation({ digest, envelope, publicKeyPem }) {
|
|
114
|
+
if (!envelope) return { state: 'missing', reason: 'no in-toto/SLSA provenance attestation was supplied for this image' };
|
|
115
|
+
|
|
116
|
+
let env;
|
|
117
|
+
try {
|
|
118
|
+
env = typeof envelope === 'string' ? JSON.parse(envelope) : envelope;
|
|
119
|
+
} catch (err) {
|
|
120
|
+
return { state: 'invalid', reason: `attestation envelope is not valid JSON: ${err.message}` };
|
|
121
|
+
}
|
|
122
|
+
if (typeof env.payload !== 'string' || typeof env.payloadType !== 'string' || !Array.isArray(env.signatures)) {
|
|
123
|
+
return { state: 'invalid', reason: 'not a recognized DSSE envelope (missing payload, payloadType, or signatures)' };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let payloadBytes;
|
|
127
|
+
let stmt;
|
|
128
|
+
try {
|
|
129
|
+
payloadBytes = Buffer.from(env.payload, 'base64');
|
|
130
|
+
stmt = JSON.parse(payloadBytes.toString('utf8'));
|
|
131
|
+
} catch (err) {
|
|
132
|
+
return { state: 'invalid', reason: `attestation payload is not a valid base64-encoded JSON statement: ${err.message}` };
|
|
133
|
+
}
|
|
134
|
+
if (!INTOTO_STATEMENT_TYPES.has(stmt._type)) {
|
|
135
|
+
return { state: 'invalid', reason: 'statement is missing a recognized in-toto "_type"' };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const digestHex = typeof digest === 'string' && digest.startsWith('sha256:') ? digest.slice('sha256:'.length) : digest;
|
|
139
|
+
const subjects = Array.isArray(stmt.subject) ? stmt.subject : [];
|
|
140
|
+
const subjectMatches = subjects.some((s) => s && s.digest && s.digest.sha256 === digestHex);
|
|
141
|
+
if (!subjectMatches) {
|
|
142
|
+
return { state: 'invalid', reason: 'attestation subject digest does not match this image', predicateType: stmt.predicateType || null };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const predicateType = stmt.predicateType || null;
|
|
146
|
+
const isSlsa = typeof predicateType === 'string' && predicateType.startsWith(SLSA_PREDICATE_PREFIX);
|
|
147
|
+
|
|
148
|
+
if (!publicKeyPem || env.signatures.length === 0) {
|
|
149
|
+
return {
|
|
150
|
+
state: 'unavailable',
|
|
151
|
+
reason: 'attestation subject matches this image, but no public key was supplied to verify its signature',
|
|
152
|
+
predicateType, isSlsa,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const pae = dssePae(env.payloadType, payloadBytes);
|
|
157
|
+
for (const sigEntry of env.signatures) {
|
|
158
|
+
if (!sigEntry || typeof sigEntry.sig !== 'string') continue;
|
|
159
|
+
let sigBytes;
|
|
160
|
+
try {
|
|
161
|
+
sigBytes = Buffer.from(sigEntry.sig, 'base64');
|
|
162
|
+
} catch {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
const { ok } = verifyRaw(pae, sigBytes, publicKeyPem);
|
|
166
|
+
if (ok) return { state: 'verified', predicateType, isSlsa };
|
|
167
|
+
}
|
|
168
|
+
return { state: 'invalid', reason: 'no signature in the attestation envelope verifies under the supplied public key', predicateType, isSlsa };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
/***/ })
|
|
173
|
+
|
|
174
|
+
};
|