Proof of Software 1.6.0: sbom-maven from the committed lockfile.json of the maven-lockfile plugin (deterministic CycloneDX 1.6, re-derived by verify --rebuild-from), --source-path . for a repository root
This commit is contained in:
parent
829d622719
commit
c3910c07a2
@ -1,7 +1,7 @@
|
|||||||
# Citations in this repository that you cannot open
|
# Citations in this repository that you cannot open
|
||||||
|
|
||||||
This file is generated by the publication stager of this repository (scripts/legaturi-declaratie.cjs in the Aere working
|
This file is generated by the publication stager of this repository (scripts/legaturi-declaratie.cjs in the Aere working
|
||||||
tree) and enforced by legaturi.cjs. It is the complete list, measured on 2026-10-12, of every path cited in this
|
tree) and enforced by legaturi.cjs. It is the complete list, measured on 2026-10-18, of every path cited in this
|
||||||
repository that does not resolve to a published file.
|
repository that does not resolve to a published file.
|
||||||
|
|
||||||
A citation is a promise that a claim is checkable. Where the target is not published, or names a file the reader creates
|
A citation is a promise that a claim is checkable. Where the target is not published, or names a file the reader creates
|
||||||
@ -12,8 +12,12 @@ wrote it.
|
|||||||
Paths that DO resolve are written relative to a side-by-side checkout of the Aere repositories, so they begin with a
|
Paths that DO resolve are written relative to a side-by-side checkout of the Aere repositories, so they begin with a
|
||||||
repository name, for example `aere-contracts/contracts/pqc/AereCryptoRegistry.sol`.
|
repository name, for example `aere-contracts/contracts/pqc/AereCryptoRegistry.sol`.
|
||||||
|
|
||||||
Unresolvable distinct paths in this repository: **3**.
|
Unresolvable distinct paths in this repository: **7**.
|
||||||
|
|
||||||
|
- `LockFileFacade.java` cited in: aere-proof-of-software/tools/proof-of-software/test/fixturi-maven/README.md
|
||||||
- `attestation.json` cited in: aere-proof-of-software/tools/proof-of-software/README.md
|
- `attestation.json` cited in: aere-proof-of-software/tools/proof-of-software/README.md
|
||||||
- `config.json` cited in: aere-proof-of-software/tools/proof-of-software/README.md
|
- `config.json` cited in: aere-proof-of-software/tools/proof-of-software/README.md
|
||||||
- `gradle/verification-metadata.xml` cited in: aere-proof-of-software/tools/proof-of-software/README.md
|
- `gradle/verification-metadata.xml` cited in: aere-proof-of-software/tools/proof-of-software/README.md
|
||||||
|
- `src/main/java/io/github/chains_project/maven_lockfile/graph/DependencyGraph.java` cited in: aere-proof-of-software/tools/proof-of-software/test/fixturi-maven/README.md
|
||||||
|
- `src/test/resources-its/it/IntegrationTestsIT/reactorSnapshotSiblingShouldSucceed/pom.xml` cited in: aere-proof-of-software/tools/proof-of-software/test/fixturi-maven/README.md
|
||||||
|
- `test/sbom-maven.test.mjs` cited in: aere-proof-of-software/tools/proof-of-software/test/fixturi-maven/README.md
|
||||||
|
|||||||
@ -44,4 +44,4 @@ the documentation are in English. No third party has reviewed this code.
|
|||||||
|
|
||||||
## Licence
|
## Licence
|
||||||
|
|
||||||
MIT, see [LICENSE](LICENSE). Files: 49 (tools/proof-of-software 37, sdk-pq-sign 6, sdk 3).
|
MIT, see [LICENSE](LICENSE). Files: 60 (tools/proof-of-software 48, sdk-pq-sign 6, sdk 3).
|
||||||
|
|||||||
@ -252,5 +252,50 @@ node test/sbom-nuget-gradle.test.mjs # 27/27 on real lock files wri
|
|||||||
node test/sbom-nuget-gradle-control-negativ.mjs # on a copy, 20 guards removed -> their own tests turn red
|
node test/sbom-nuget-gradle-control-negativ.mjs # on a copy, 20 guards removed -> their own tests turn red
|
||||||
```
|
```
|
||||||
|
|
||||||
Maven (`pom.xml` without a lock file) is not done: Maven has no lock file of its own, and an SBOM derived from `pom.xml` ranges would
|
## A Maven project's SBOM, from the committed lockfile.json (1.6.0)
|
||||||
say what was asked for, not what was resolved.
|
|
||||||
|
```
|
||||||
|
mvn io.github.chains-project:maven-lockfile:generate # the project's own step, once per module; commit lockfile.json
|
||||||
|
node pos.mjs sbom-maven --source-path . --out sbom.cdx.json # from the COMMITTED lockfile.json and pom.xml; . is the repository root
|
||||||
|
node pos.mjs attest --source-path . --sbom sbom.cdx.json --name myapp --version 1.4.2 --out attestation.json target/myapp-1.4.2.jar
|
||||||
|
node pos.mjs verify attestation.json --rebuild-from <a clone you chose> target/myapp-1.4.2.jar sbom.cdx.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Maven has no lock file of its own: `pom.xml` declares ranges and inheritance, and what gets resolved depends on the toolchain and on the
|
||||||
|
registry at build time, so an SBOM derived from `pom.xml` would say what was asked for, not what was resolved. The
|
||||||
|
[maven-lockfile plugin](https://github.com/chains-project/maven-lockfile) (`io.github.chains-project:maven-lockfile`) writes the RESOLVED
|
||||||
|
graph to `lockfile.json`, one per module: every artifact with its resolved version, classifier and type, its scope, the checksum of the
|
||||||
|
file (read from the repository in `remote` mode, computed on the file in `local` mode), Maven's mediation verdict (`selectedVersion`,
|
||||||
|
`included`), the plugins with their own dependencies, parent POMs and BOMs, and the checksum of the `pom.xml` it was generated from.
|
||||||
|
`sbom-maven` reads only `<commit>:<path>/lockfile.json` and `pom.xml`, with no Maven and no network, and the same files always give the
|
||||||
|
same bytes (no timestamp; the serial number is derived from the content). Its components are the artifacts the lockfile resolved (the
|
||||||
|
nodes marked `included`, plus the artifacts the plugin recorded without a mediation verdict in hermetic mode, marked
|
||||||
|
`aere:maven-recorded=dynamic`), once per `pkg:maven` purl (`?classifier=`, `?type=` when not jar), each with its checksum as a CycloneDX
|
||||||
|
hash, its scopes (`aere:maven-scope`) and its roles (`aere:maven-role`: dependency, plugin, plugin-dependency, parent-pom, bom). Measured on
|
||||||
|
the plugin's own repository: the lockfile checksums of `gson-2.14.0.jar`, `guava-33.5.0-jre.jar` and `gson-parent-2.14.0.pom` are exactly
|
||||||
|
the `.sha256` files Maven Central publishes for them. The dependency graph is the lockfile's tree, and a child Maven omitted for conflict
|
||||||
|
or for duplicate points to the version that was SELECTED, which is what reaches the classpath; the omitted nodes are counted
|
||||||
|
(`aere:maven-omitted-for-conflict`, `-for-duplicate`), not listed. The committed `pom.xml` is hashed and compared with the `pom.checksum`
|
||||||
|
the lockfile recorded (measured equal on the plugin's own repository), so `aere:maven-pom-matches-lockfile` says whether the lockfile was
|
||||||
|
generated from this very `pom.xml`: a lockfile older than the pom is a measured fact here, not a caveat. `verify --rebuild-from`
|
||||||
|
re-derives the SBOM from the committed tree and compares what the two say; an SBOM edited by hand (a component dropped, a version, a
|
||||||
|
hash, the root, an edge, a scope, a plugin, the pom verdict) passes on its digest and fails here.
|
||||||
|
|
||||||
|
What it does NOT say: the plugin must have run, and the lockfile is what it recorded then; a project without a committed `lockfile.json`
|
||||||
|
has no deterministic SBOM here and is reported by name. The plugin writes one lockfile per module, so this SBOM is per module: an
|
||||||
|
aggregator `pom.xml` with `<modules>` and no lockfile is refused with the module names, and a module's SBOM names its siblings
|
||||||
|
(`aere:maven-modules`) and which of them have a committed lockfile. The build follows the lockfile only when the project runs the plugin's
|
||||||
|
`validate` or `freeze`; otherwise what Maven resolves today can differ from it. The `pom.xml` comparison covers the module's own pom, not
|
||||||
|
its parents, and Maven profiles (modules or dependencies a profile activates) are not read. An artifact with an empty checksum (a
|
||||||
|
SNAPSHOT module of the same reactor) is listed without a hash and counted (`aere:maven-without-checksum`). A lockfile written with
|
||||||
|
`reduced=true` carries no mediated nodes, so those counts are zero by construction. `--source-path .` names the repository root (the usual
|
||||||
|
Maven layout) and works for every `sbom-*` command and for `attest` from 1.6.0 (until then the root was refused).
|
||||||
|
|
||||||
|
```
|
||||||
|
node test/sbom-maven.test.mjs # 26/26 on real lockfiles written by the plugin (test/fixturi-maven: the plugin's own repository
|
||||||
|
# at 083b879a, 617 components; a reactor module; a classifier project): deterministic, every hash
|
||||||
|
# equal to the lockfile's (617/617), the pom checksum equal to the SHA-256 of the committed pom.xml,
|
||||||
|
# the graph checked node by node against an independent walk, eight hand edits caught, broken inputs
|
||||||
|
# refused with the reason; the SBOM of a lockfile at the repository root attested with source.path "."
|
||||||
|
node test/sbom-maven-control-negativ.mjs # on a copy, 16 guards removed -> their own tests turn red
|
||||||
|
```
|
||||||
|
|||||||
@ -12,6 +12,9 @@
|
|||||||
// packages.lock.json and the project file next to it; deterministic, re-derived by verify --rebuild-from)
|
// packages.lock.json and the project file next to it; deterministic, re-derived by verify --rebuild-from)
|
||||||
// node pos.mjs sbom-gradle --source-path DIR --version V [--commit C] [--out sbom.cdx.json] (1.5.0: Gradle, from the COMMITTED
|
// node pos.mjs sbom-gradle --source-path DIR --version V [--commit C] [--out sbom.cdx.json] (1.5.0: Gradle, from the COMMITTED
|
||||||
// gradle/verification-metadata.xml and gradle.lockfile; deterministic, re-derived by verify --rebuild-from)
|
// gradle/verification-metadata.xml and gradle.lockfile; deterministic, re-derived by verify --rebuild-from)
|
||||||
|
// node pos.mjs sbom-maven --source-path DIR [--version V] [--commit C] [--out sbom.cdx.json] (1.6.0: Maven, from the COMMITTED
|
||||||
|
// lockfile.json of the maven-lockfile plugin and the pom.xml next to it; deterministic, re-derived by verify
|
||||||
|
// --rebuild-from; --source-path . is the repository root)
|
||||||
// node pos.mjs model-bom --model-dir DIR --out mlbom.json [--name N --version V] [--task T] [--dataset NAME=FILE ...]
|
// node pos.mjs model-bom --model-dir DIR --out mlbom.json [--name N --version V] [--task T] [--dataset NAME=FILE ...]
|
||||||
// node pos.mjs pubkey --keys keys.json [--out pub.json] (the public part, to hand out)
|
// node pos.mjs pubkey --keys keys.json [--out pub.json] (the public part, to hand out)
|
||||||
// node pos.mjs credential issue --issuer-keys org.json --issuer-name O --subject-pub dev.pub.json --subject-name D [--valid-days 365]
|
// node pos.mjs credential issue --issuer-keys org.json --issuer-name O --subject-pub dev.pub.json --subject-name D [--valid-days 365]
|
||||||
@ -45,7 +48,7 @@ import { fileURLToPath } from 'node:url';
|
|||||||
import * as pq from '../../sdk-pq-sign/index.mjs';
|
import * as pq from '../../sdk-pq-sign/index.mjs';
|
||||||
import { AereCloud } from '../../sdk/index.mjs';
|
import { AereCloud } from '../../sdk/index.mjs';
|
||||||
|
|
||||||
export const TOOL = 'aere-proof-of-software/1.5.0';
|
export const TOOL = 'aere-proof-of-software/1.6.0';
|
||||||
const AICI = path.dirname(fileURLToPath(import.meta.url));
|
const AICI = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
// peste prag, in flux: o greutate de model de cativa GB nu are voie sa fie citita intreaga in memorie (2026-09-25)
|
// peste prag, in flux: o greutate de model de cativa GB nu are voie sa fie citita intreaga in memorie (2026-09-25)
|
||||||
@ -64,6 +67,10 @@ export function sha256File(p) {
|
|||||||
const sha256Text = (t) => '0x' + crypto.createHash('sha256').update(t, 'utf8').digest('hex');
|
const sha256Text = (t) => '0x' + crypto.createHash('sha256').update(t, 'utf8').digest('hex');
|
||||||
|
|
||||||
const git = (dir, args) => { try { return execFileSync('git', args, { cwd: dir, stdio: ['ignore', 'pipe', 'ignore'], maxBuffer: 1 << 28 }).toString().trim(); } catch { return null; } };
|
const git = (dir, args) => { try { return execFileSync('git', args, { cwd: dir, stdio: ['ignore', 'pipe', 'ignore'], maxBuffer: 1 << 28 }).toString().trim(); } catch { return null; } };
|
||||||
|
// 1.6.0 (2026-10-18): `--source-path .` numeste radacina depozitului (un pom.xml sau un go.mod la radacina, cazul obisnuit): in declaratie
|
||||||
|
// calea e '.', iar git vrea `<commit>:` pentru arborele radacina si `<commit>:fisier` pentru un fisier din el; `<commit>:.` e REFUZAT
|
||||||
|
// de git ("exists on disk, but not in HEAD", masurat), de aceea calea se construieste intr-un singur loc.
|
||||||
|
const caleGit = (commit, rel, f = '') => `${commit}:${!rel || rel === '.' ? '' : rel + (f ? '/' : '')}${f}`;
|
||||||
|
|
||||||
// With sourcePath the statement also names the git TREE of that directory at HEAD. A tree hash is a digest of content:
|
// With sourcePath the statement also names the git TREE of that directory at HEAD. A tree hash is a digest of content:
|
||||||
// two checkouts with the same tree hold the same committed bytes, whatever the line endings of their working copies.
|
// two checkouts with the same tree hold the same committed bytes, whatever the line endings of their working copies.
|
||||||
@ -79,9 +86,9 @@ function gitInfo(dir, sourcePath) {
|
|||||||
};
|
};
|
||||||
if (sourcePath) {
|
if (sourcePath) {
|
||||||
const top = git(dir, ['rev-parse', '--show-toplevel']);
|
const top = git(dir, ['rev-parse', '--show-toplevel']);
|
||||||
const rel = path.relative(path.resolve(top), path.resolve(dir, sourcePath)).split(path.sep).join('/');
|
const rel = path.relative(path.resolve(top), path.resolve(dir, sourcePath)).split(path.sep).join('/') || '.';
|
||||||
if (!rel || rel.startsWith('..')) throw new Error(`--source-path ${sourcePath}: must be a directory below the repository root`);
|
if (rel.startsWith('..')) throw new Error(`--source-path ${sourcePath}: must be the repository root or a directory below it`);
|
||||||
const tree = git(dir, ['rev-parse', '--verify', '--quiet', `HEAD:${rel}`]);
|
const tree = git(dir, ['rev-parse', '--verify', '--quiet', caleGit('HEAD', rel)]);
|
||||||
if (!tree) throw new Error(`--source-path ${sourcePath}: "${rel}" is not tracked at HEAD`);
|
if (!tree) throw new Error(`--source-path ${sourcePath}: "${rel}" is not tracked at HEAD`);
|
||||||
const pd = git(dir, ['status', '--porcelain', '--', path.resolve(dir, sourcePath)]);
|
const pd = git(dir, ['status', '--porcelain', '--', path.resolve(dir, sourcePath)]);
|
||||||
info.path = rel; info.tree = tree; info.pathDirty = pd === null ? null : pd.length > 0;
|
info.path = rel; info.tree = tree; info.pathDirty = pd === null ? null : pd.length > 0;
|
||||||
@ -300,7 +307,7 @@ export function sbomGo({ goSum, goMod, version }) {
|
|||||||
}
|
}
|
||||||
/** SBOM-ul Go al arborelui COMIS <commit>:<cale> (go.sum si go.mod citite din git, fara unealta Go). */
|
/** SBOM-ul Go al arborelui COMIS <commit>:<cale> (go.sum si go.mod citite din git, fara unealta Go). */
|
||||||
export function sbomGoFromTree(repoDir, commit, rel, version) {
|
export function sbomGoFromTree(repoDir, commit, rel, version) {
|
||||||
const blob = (f) => { try { return execFileSync('git', ['cat-file', '-p', `${commit}:${rel}/${f}`], { cwd: repoDir, stdio: ['ignore', 'pipe', 'ignore'], maxBuffer: 1 << 28 }).toString(); } catch { return null; } };
|
const blob = (f) => { try { return execFileSync('git', ['cat-file', '-p', caleGit(commit, rel, f)], { cwd: repoDir, stdio: ['ignore', 'pipe', 'ignore'], maxBuffer: 1 << 28 }).toString(); } catch { return null; } };
|
||||||
const goMod = blob('go.mod'); if (goMod == null) return { lipsa: 'the committed tree has no go.mod, so the SBOM cannot be re-derived from it' };
|
const goMod = blob('go.mod'); if (goMod == null) return { lipsa: 'the committed tree has no go.mod, so the SBOM cannot be re-derived from it' };
|
||||||
const goSum = blob('go.sum'); if (goSum == null) return { lipsa: 'the committed tree has no go.sum, so the SBOM cannot be re-derived from it' };
|
const goSum = blob('go.sum'); if (goSum == null) return { lipsa: 'the committed tree has no go.sum, so the SBOM cannot be re-derived from it' };
|
||||||
try { return { bom: sbomGo({ goSum, goMod, version }) }; } catch (e) { return { lipsa: 'the committed go.sum or go.mod cannot be read: ' + String(e.message || e).slice(0, 100) }; }
|
try { return { bom: sbomGo({ goSum, goMod, version }) }; } catch (e) { return { lipsa: 'the committed go.sum or go.mod cannot be read: ' + String(e.message || e).slice(0, 100) }; }
|
||||||
@ -316,8 +323,10 @@ function serieDerivata(o) {
|
|||||||
const propr = (o) => Object.entries(o).filter(([, v]) => v != null).map(([name, value]) => ({ name, value: String(value) })).sort((a, b) => (a.name < b.name ? -1 : 1));
|
const propr = (o) => Object.entries(o).filter(([, v]) => v != null).map(([name, value]) => ({ name, value: String(value) })).sort((a, b) => (a.name < b.name ? -1 : 1));
|
||||||
const graf = (muchii) => { const m = new Map(); for (const [de, la] of muchii) { if (!m.has(de)) m.set(de, new Set()); m.get(de).add(la); }
|
const graf = (muchii) => { const m = new Map(); for (const [de, la] of muchii) { if (!m.has(de)) m.set(de, new Set()); m.get(de).add(la); }
|
||||||
return [...m.entries()].map(([ref, s]) => ({ ref, dependsOn: [...s].sort() })).sort((a, b) => (a.ref < b.ref ? -1 : 1)); };
|
return [...m.entries()].map(([ref, s]) => ({ ref, dependsOn: [...s].sort() })).sort((a, b) => (a.ref < b.ref ? -1 : 1)); };
|
||||||
const blobDin = (repoDir, commit, cale) => { try { return execFileSync('git', ['cat-file', '-p', `${commit}:${cale}`], { cwd: repoDir, stdio: ['ignore', 'pipe', 'ignore'], maxBuffer: 1 << 28 }).toString(); } catch { return null; } };
|
const blobOctetiDin = (repoDir, commit, rel, f) => { try { return execFileSync('git', ['cat-file', '-p', caleGit(commit, rel, f)], { cwd: repoDir, stdio: ['ignore', 'pipe', 'ignore'], maxBuffer: 1 << 28 }); } catch { return null; } };
|
||||||
const numeDin = (repoDir, commit, rel) => { try { return execFileSync('git', ['ls-tree', '--name-only', `${commit}:${rel}`], { cwd: repoDir, stdio: ['ignore', 'pipe', 'ignore'] }).toString().split('\n').filter(Boolean); } catch { return null; } };
|
const blobDin = (repoDir, commit, rel, f) => { const b = blobOctetiDin(repoDir, commit, rel, f); return b == null ? null : b.toString(); };
|
||||||
|
const existaIn = (repoDir, commit, rel, f) => { try { execFileSync('git', ['cat-file', '-e', caleGit(commit, rel, f)], { cwd: repoDir, stdio: 'ignore' }); return true; } catch { return false; } };
|
||||||
|
const numeDin = (repoDir, commit, rel) => { try { return execFileSync('git', ['ls-tree', '--name-only', caleGit(commit, rel)], { cwd: repoDir, stdio: ['ignore', 'pipe', 'ignore'] }).toString().split('\n').filter(Boolean); } catch { return null; } };
|
||||||
|
|
||||||
// 1.5.0 (2026-09-29): SBOM-ul unui proiect .NET, derivat DETERMINIST din packages.lock.json COMIS (scris de NuGet cu
|
// 1.5.0 (2026-09-29): SBOM-ul unui proiect .NET, derivat DETERMINIST din packages.lock.json COMIS (scris de NuGet cu
|
||||||
// RestorePackagesWithLockFile). Componentele sunt pachetele din toate sectiunile (cadru, si cadru/RID), cu versiunea REZOLVATA;
|
// RestorePackagesWithLockFile). Componentele sunt pachetele din toate sectiunile (cadru, si cadru/RID), cu versiunea REZOLVATA;
|
||||||
@ -372,7 +381,7 @@ export function sbomNuget({ lock, projectName, version }) {
|
|||||||
}
|
}
|
||||||
/** SBOM-ul .NET al arborelui COMIS <commit>:<cale>: packages.lock.json si numele UNUI fisier de proiect din acelasi dosar. */
|
/** SBOM-ul .NET al arborelui COMIS <commit>:<cale>: packages.lock.json si numele UNUI fisier de proiect din acelasi dosar. */
|
||||||
export function sbomNugetFromTree(repoDir, commit, rel, version) {
|
export function sbomNugetFromTree(repoDir, commit, rel, version) {
|
||||||
const lock = blobDin(repoDir, commit, `${rel}/packages.lock.json`);
|
const lock = blobDin(repoDir, commit, rel, 'packages.lock.json');
|
||||||
if (lock == null) return { lipsa: 'the committed tree has no packages.lock.json, so the SBOM cannot be re-derived from it' };
|
if (lock == null) return { lipsa: 'the committed tree has no packages.lock.json, so the SBOM cannot be re-derived from it' };
|
||||||
const proj = (numeDin(repoDir, commit, rel) || []).filter((n) => /\.(cs|fs|vb)proj$/i.test(n));
|
const proj = (numeDin(repoDir, commit, rel) || []).filter((n) => /\.(cs|fs|vb)proj$/i.test(n));
|
||||||
if (proj.length !== 1) return { lipsa: `the committed directory has ${proj.length} project files; exactly one names the SBOM root` };
|
if (proj.length !== 1) return { lipsa: `the committed directory has ${proj.length} project files; exactly one names the SBOM root` };
|
||||||
@ -420,7 +429,7 @@ function xmlTokeni(x, unde) {
|
|||||||
if (lt > i) out.push({ text: x.slice(i, lt) });
|
if (lt > i) out.push({ text: x.slice(i, lt) });
|
||||||
if (x.startsWith('<!--', lt)) { const e = x.indexOf('-->', lt + 4); if (e === -1) throw new Error(`${unde}: a comment is not closed`); i = e + 3; continue; }
|
if (x.startsWith('<!--', lt)) { const e = x.indexOf('-->', lt + 4); if (e === -1) throw new Error(`${unde}: a comment is not closed`); i = e + 3; continue; }
|
||||||
if (x.startsWith('<?', lt)) { const e = x.indexOf('?>', lt + 2); if (e === -1) throw new Error(`${unde}: a declaration is not closed`); i = e + 2; continue; }
|
if (x.startsWith('<?', lt)) { const e = x.indexOf('?>', lt + 2); if (e === -1) throw new Error(`${unde}: a declaration is not closed`); i = e + 2; continue; }
|
||||||
if (x.startsWith('<!', lt)) throw new Error(`${unde}: a DOCTYPE or CDATA section, which Gradle does not write`);
|
if (x.startsWith('<!', lt)) throw new Error(`${unde}: a DOCTYPE or CDATA section, which this reader does not accept (neither Gradle nor Maven writes one)`);
|
||||||
// capatul tagului, cu ghilimelele sarite ca un bloc (un `>` intr-o valoare nu inchide tagul); fiecare caracter vazut o data
|
// capatul tagului, cu ghilimelele sarite ca un bloc (un `>` intr-o valoare nu inchide tagul); fiecare caracter vazut o data
|
||||||
let j = lt + 1;
|
let j = lt + 1;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
@ -533,17 +542,208 @@ export function sbomGradle({ verificationMetadata, lockfile = null, projectName,
|
|||||||
export const RADACINA_GRADLE = /^[ \t]*rootProject\.name[ \t]*=[ \t]*['"]([^'"\r\n]+)['"][ \t]*\r?$/m;
|
export const RADACINA_GRADLE = /^[ \t]*rootProject\.name[ \t]*=[ \t]*['"]([^'"\r\n]+)['"][ \t]*\r?$/m;
|
||||||
/** SBOM-ul Gradle al arborelui COMIS <commit>:<cale>: gradle/verification-metadata.xml, gradle.lockfile (daca e), rootProject.name. */
|
/** SBOM-ul Gradle al arborelui COMIS <commit>:<cale>: gradle/verification-metadata.xml, gradle.lockfile (daca e), rootProject.name. */
|
||||||
export function sbomGradleFromTree(repoDir, commit, rel, version) {
|
export function sbomGradleFromTree(repoDir, commit, rel, version) {
|
||||||
const vm = blobDin(repoDir, commit, `${rel}/gradle/verification-metadata.xml`);
|
const vm = blobDin(repoDir, commit, rel, 'gradle/verification-metadata.xml');
|
||||||
if (vm == null) return { lipsa: 'the committed tree has no gradle/verification-metadata.xml, so the SBOM cannot be re-derived from it' };
|
if (vm == null) return { lipsa: 'the committed tree has no gradle/verification-metadata.xml, so the SBOM cannot be re-derived from it' };
|
||||||
const setari = blobDin(repoDir, commit, `${rel}/settings.gradle`) ?? blobDin(repoDir, commit, `${rel}/settings.gradle.kts`);
|
const setari = blobDin(repoDir, commit, rel, 'settings.gradle') ?? blobDin(repoDir, commit, rel, 'settings.gradle.kts');
|
||||||
// B-22: `^\s*` cu /m trece peste randuri goale si se reia de la fiecare rand (patratic: 80.000 de randuri goale -> 5 s); spatiile
|
// B-22: `^\s*` cu /m trece peste randuri goale si se reia de la fiecare rand (patratic: 80.000 de randuri goale -> 5 s); spatiile
|
||||||
// se cauta numai in rand
|
// se cauta numai in rand
|
||||||
const nm = setari && RADACINA_GRADLE.exec(setari);
|
const nm = setari && RADACINA_GRADLE.exec(setari);
|
||||||
if (!nm) return { lipsa: 'the committed settings.gradle(.kts) sets no rootProject.name, which names the SBOM root' };
|
if (!nm) return { lipsa: 'the committed settings.gradle(.kts) sets no rootProject.name, which names the SBOM root' };
|
||||||
try { return { bom: sbomGradle({ verificationMetadata: vm, lockfile: blobDin(repoDir, commit, `${rel}/gradle.lockfile`), projectName: nm[1], version }) }; }
|
try { return { bom: sbomGradle({ verificationMetadata: vm, lockfile: blobDin(repoDir, commit, rel, 'gradle.lockfile'), projectName: nm[1], version }) }; }
|
||||||
catch (e) { return { lipsa: 'the committed Gradle verification metadata cannot be read: ' + String(e.message || e).slice(0, 120) }; }
|
catch (e) { return { lipsa: 'the committed Gradle verification metadata cannot be read: ' + String(e.message || e).slice(0, 120) }; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 1.6.0 (2026-10-18): SBOM-ul unui proiect Maven, derivat DETERMINIST din lockfile.json COMIS, scris de pluginul maven-lockfile
|
||||||
|
// (io.github.chains-project:maven-lockfile, scopul `generate`: cate un lockfile.json in dosarul FIECARUI modul). Maven nu are un fisier
|
||||||
|
// de blocare al lui: pom.xml declara intervale si mosteniri, iar rezolvarea cere unealta si registrul; lockfile-ul pluginului tine
|
||||||
|
// graful REZOLVAT. Citit din sursa pluginului (DependencyGraph.java, LockFileFacade.java, JsonUtils.java la 083b879a), nu din memorie:
|
||||||
|
// - fiecare nod: groupId, artifactId, version, classifier si type (lipsesc cand sunt goale, respectiv jar), scope, checksumAlgorithm +
|
||||||
|
// checksum (hash-ul FISIERULUI rezolvat: modul `remote` il citeste din depozit, `local` il calculeaza pe fisier; GOL pentru un modul
|
||||||
|
// SNAPSHOT din acelasi reactor), selectedVersion + included din medierea lui Maven, id = g:a:v, children, parentPom, boms;
|
||||||
|
// - `included: false` CU `selectedVersion` = nod scos de mediere (conflict; duplicat cand selectedVersion == version): nu ajunge pe
|
||||||
|
// classpath, iar parintele lui primeste de fapt versiunea ALEASA; FARA `selectedVersion` = artefact inregistrat dinamic (modul
|
||||||
|
// hermetic: atasat unui plugin cu DependencyNode.of, fara mediere): rezolvat, deci componenta (`aere:maven-recorded=dynamic`);
|
||||||
|
// - mavenPlugins si mavenExtensions, fiecare cu propriile dependinte; pom-urile parinte si BOM-urile, cu checksum;
|
||||||
|
// - pom.checksum = hash-ul pom.xml-ului din care s-a generat (masurat 2026-10-18: egal cu SHA-256 al pom.xml COMIS in depozitul
|
||||||
|
// pluginului), deci "lockfile-ul e mai vechi decat pom-ul" se MASOARA (`aere:maven-pom-matches-lockfile`), nu se presupune.
|
||||||
|
// Componente: o data pe purl (pkg:maven/<groupId>/<artifactId>@<version>?classifier=..&type=.., tipul jar nu se scrie, calificatorii in
|
||||||
|
// ordinea cheilor: purl-spec types/maven-definition.json si tests/types/maven-test.json), checksum-ul ca `hashes` cand algoritmul e unul
|
||||||
|
// CycloneDX (numele din MessageDigest pe care le scrie pluginul coincid), scope-urile si rolurile (dependency, plugin, plugin-dependency,
|
||||||
|
// extension, extension-dependency, parent-pom, bom) ca proprietati; aceeasi coordonata in doua arbori (proiect si un plugin) e O componenta
|
||||||
|
// cu rolurile adunate, si checksum-ul trebuie sa fie acelasi. Graful: radacina -> nodurile de sus, parinte -> copil (un copil mediat trimite
|
||||||
|
// la versiunea aleasa; refuz daca niciun nod inclus nu o are), plugin -> dependintele lui; pom-urile nu au muchii. Modulele din pom.xml
|
||||||
|
// (<modules>, nu si cele din profile) se numesc, cu care au lockfile.json comis: SBOM-ul e al ACESTUI modul.
|
||||||
|
const MAVEN_SCOPES = new Set(['compile', 'provided', 'runtime', 'test', 'system', 'import']);
|
||||||
|
const MAVEN_HEX = { 'SHA-256': 64, 'SHA-512': 128, 'SHA-384': 96, 'SHA-1': 40, MD5: 32, 'SHA3-256': 64, 'SHA3-384': 96, 'SHA3-512': 128 };
|
||||||
|
const MAVEN_NODE_HASH = { 'SHA-256': 'sha256', 'SHA-512': 'sha512', 'SHA-384': 'sha384', 'SHA-1': 'sha1', MD5: 'md5', 'SHA3-256': 'sha3-256', 'SHA3-384': 'sha3-384', 'SHA3-512': 'sha3-512' };
|
||||||
|
export const purlMavenQ = (g, a, v, classifier, type) => {
|
||||||
|
const q = [['classifier', classifier || null], ['type', type && type !== 'jar' ? type : null]].filter(([, x]) => x).map(([k, x]) => `${k}=${encodeURIComponent(x)}`);
|
||||||
|
return purlMaven(g, a, v) + (q.length ? '?' + q.join('&') : '');
|
||||||
|
};
|
||||||
|
/** Citirea lui pom.xml: coordonatele (cu mostenirea din <parent>), packaging si <modules> de la nivelul proiectului (nu cele din profile). */
|
||||||
|
export function citestePom(text) {
|
||||||
|
const U = 'pom.xml';
|
||||||
|
const tok = xmlTokeni(String(text || ''), U);
|
||||||
|
const stiva = []; let radacina = false;
|
||||||
|
const p = { groupId: null, artifactId: null, version: null, packaging: null, parent: { groupId: null, artifactId: null, version: null }, modules: [] };
|
||||||
|
const camp = { 'project/groupId': 'groupId', 'project/artifactId': 'artifactId', 'project/version': 'version', 'project/packaging': 'packaging' };
|
||||||
|
for (const t of tok) {
|
||||||
|
if (t.text != null) {
|
||||||
|
const v = t.text.trim(); if (!v) continue;
|
||||||
|
const cale = stiva.join('/');
|
||||||
|
if (camp[cale]) p[camp[cale]] = v;
|
||||||
|
else if (cale === 'project/parent/groupId' || cale === 'project/parent/artifactId' || cale === 'project/parent/version') p.parent[cale.split('/')[2]] = v;
|
||||||
|
else if (cale === 'project/modules/module') p.modules.push(v);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (t.open) {
|
||||||
|
if (!stiva.length) {
|
||||||
|
if (radacina) throw new Error(`${U}: a second root element <${t.open}>`);
|
||||||
|
if (t.open !== 'project') throw new Error(`${U} has no <project> element`);
|
||||||
|
radacina = true;
|
||||||
|
}
|
||||||
|
if (!t.self) stiva.push(t.open);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const scos = stiva.pop();
|
||||||
|
// (scris cu termenii inversati fata de cititorul Gradle, dinadins: controlul negativ al aceluia isi ancoreaza plantarea pe randul lui)
|
||||||
|
if (t.close !== scos) throw new Error(`${U}: </${t.close}> closes <${scos || 'nothing'}>`);
|
||||||
|
}
|
||||||
|
if (!radacina) throw new Error(`${U} has no <project> element`);
|
||||||
|
if (stiva.length) throw new Error(`${U}: <${stiva[stiva.length - 1]}> is not closed`);
|
||||||
|
if (!p.artifactId) throw new Error(`${U} names no artifactId`);
|
||||||
|
return { ...p, groupId: p.groupId || p.parent.groupId, version: p.version || p.parent.version };
|
||||||
|
}
|
||||||
|
export function sbomMaven({ lock, pom, version = null, moduleAreLockfile = null }) {
|
||||||
|
let j; try { j = typeof lock === 'string' ? JSON.parse(lock) : lock; } catch { throw new Error('lockfile.json is not JSON'); }
|
||||||
|
if (!j || typeof j !== 'object' || Array.isArray(j)) throw new Error('lockfile.json is not a JSON object');
|
||||||
|
if (j.lockFileVersion !== 1) throw new Error(`lockfile.json has lockFileVersion ${JSON.stringify(j.lockFileVersion)}; this reader knows version 1`);
|
||||||
|
const str = (x) => typeof x === 'string' && x.length > 0;
|
||||||
|
const gId = j.groupId ?? j.groupID, aId = j.artifactId ?? j.artifactID; // formele vechi, `alternate` in LockFile.java
|
||||||
|
if (!str(gId) || !str(aId) || !str(j.version)) throw new Error('lockfile.json names no project (groupId, artifactId, version)');
|
||||||
|
if (!Array.isArray(j.dependencies)) throw new Error('lockfile.json has no dependencies list');
|
||||||
|
if (pom == null) throw new Error('an SBOM for a Maven project needs the committed pom.xml next to lockfile.json');
|
||||||
|
const pomOcteti = Buffer.isBuffer(pom) ? pom : Buffer.from(String(pom), 'utf8');
|
||||||
|
const comp = new Map(), muchii = [], omise = { conflict: 0, duplicate: 0 };
|
||||||
|
const hashDe = (alg, sum, unde) => {
|
||||||
|
if (sum == null || sum === '') return null; // gol: modul din acelasi reactor sau radacina (DependencyGraph.java)
|
||||||
|
if (!str(sum) || !str(alg)) throw new Error(`lockfile.json: ${unde} has a checksum without an algorithm`);
|
||||||
|
if (MAVEN_HEX[alg] && !new RegExp(`^[0-9a-f]{${MAVEN_HEX[alg]}}$`, 'i').test(sum)) throw new Error(`lockfile.json: ${unde} has a checksum that is not ${alg} hex`);
|
||||||
|
return { alg, sum: sum.toLowerCase() };
|
||||||
|
};
|
||||||
|
// o componenta pe purl; a doua aparitie (alt arbore, alt plugin) aduna scope-uri si roluri, iar checksum-ul trebuie sa fie ACELASI
|
||||||
|
const adauga = ({ g, a, v, classifier = null, type = null, alg, sum, scope = null, rol, repo = null, dinamic = false }, unde) => {
|
||||||
|
const purl = purlMavenQ(g, a, v, classifier, type);
|
||||||
|
const h = hashDe(alg, sum, unde);
|
||||||
|
const c = comp.get(purl) || { g, a, v, hash: h, scopes: new Set(), roluri: new Set(), repos: new Set(), dinamic: false };
|
||||||
|
if (comp.has(purl) && (c.hash && c.hash.sum) !== (h && h.sum)) throw new Error(`lockfile.json: ${purl} has two different checksums`);
|
||||||
|
if (scope) c.scopes.add(scope); c.roluri.add(rol); if (str(repo)) c.repos.add(repo); if (dinamic) c.dinamic = true;
|
||||||
|
comp.set(purl, c); return purl;
|
||||||
|
};
|
||||||
|
// lantul pom-urilor parinte si BOM-urile: componente de tip pom, fara muchii
|
||||||
|
const pomuri = (p, rol, unde) => {
|
||||||
|
for (let x = p, k = 0; x != null; x = x.parent, k++) {
|
||||||
|
if (typeof x !== 'object' || !str(x.groupId) || !str(x.artifactId) || !str(x.version)) throw new Error(`lockfile.json: a ${rol} under ${unde} has no groupId, artifactId or version`);
|
||||||
|
const id = `${x.groupId}:${x.artifactId}:${x.version}`;
|
||||||
|
adauga({ g: x.groupId, a: x.artifactId, v: x.version, type: 'pom', alg: x.checksumAlgorithm, sum: x.checksum, rol: k ? 'parent-pom' : rol, repo: x.repositoryId }, `${k ? 'parent-pom' : rol} ${id}`);
|
||||||
|
for (const b of x.boms || []) pomuri(b, 'bom', id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const nod = (n, deLa, rol, unde) => {
|
||||||
|
if (!n || typeof n !== 'object' || !str(n.groupId) || !str(n.artifactId) || !str(n.version)) throw new Error(`lockfile.json: a dependency under ${unde} has no groupId, artifactId or version`);
|
||||||
|
const id = `${n.groupId}:${n.artifactId}:${n.version}`;
|
||||||
|
if (n.id != null && n.id !== id) throw new Error(`lockfile.json: id ${n.id} does not name ${id}`);
|
||||||
|
const scope = n.scope == null || n.scope === '' ? 'compile' : n.scope; // MavenScope.fromString: gol inseamna compile
|
||||||
|
if (!MAVEN_SCOPES.has(scope)) throw new Error(`lockfile.json: ${id} has an unknown scope ${JSON.stringify(n.scope)}`);
|
||||||
|
const classifier = str(n.classifier) ? n.classifier : null, type = str(n.type) ? n.type : null;
|
||||||
|
if (n.included === true && str(n.selectedVersion) && n.selectedVersion !== n.version) throw new Error(`lockfile.json: ${id} is included and names another selected version ${n.selectedVersion}`);
|
||||||
|
const mediat = n.included !== true && n.selectedVersion != null;
|
||||||
|
if (mediat) {
|
||||||
|
if (!str(n.selectedVersion)) throw new Error(`lockfile.json: ${id} is omitted by mediation without a selected version`);
|
||||||
|
if ((n.children || []).length) throw new Error(`lockfile.json: ${id} is omitted by mediation and still has children`);
|
||||||
|
omise[n.selectedVersion === n.version ? 'duplicate' : 'conflict']++;
|
||||||
|
muchii.push([deLa, purlMavenQ(n.groupId, n.artifactId, n.selectedVersion, classifier, type), `${id} (mediated to ${n.selectedVersion})`]); // tinta se cere la sfarsit
|
||||||
|
} else {
|
||||||
|
const ref = adauga({ g: n.groupId, a: n.artifactId, v: n.version, classifier, type, alg: n.checksumAlgorithm, sum: n.checksum, scope, rol, repo: n.repositoryId, dinamic: n.selectedVersion == null }, id);
|
||||||
|
muchii.push([deLa, ref, id]);
|
||||||
|
for (const c of n.children || []) nod(c, ref, rol, id);
|
||||||
|
}
|
||||||
|
pomuri(n.parentPom, 'parent-pom', id);
|
||||||
|
for (const b of n.boms || []) pomuri(b, 'bom', id);
|
||||||
|
};
|
||||||
|
const ver = version || j.version;
|
||||||
|
const root = purlMaven(gId, aId, ver);
|
||||||
|
for (const d of j.dependencies) nod(d, root, 'dependency', 'the project');
|
||||||
|
const unelte = (lista, rol, ce) => {
|
||||||
|
if (lista == null) return 0;
|
||||||
|
if (!Array.isArray(lista)) throw new Error(`lockfile.json: ${ce} is not a list`);
|
||||||
|
for (const p of lista) {
|
||||||
|
if (!p || typeof p !== 'object' || !str(p.groupId) || !str(p.artifactId) || !str(p.version)) throw new Error(`lockfile.json: a ${rol} has no groupId, artifactId or version`);
|
||||||
|
const id = `${p.groupId}:${p.artifactId}:${p.version}`;
|
||||||
|
const ref = adauga({ g: p.groupId, a: p.artifactId, v: p.version, alg: p.checksumAlgorithm, sum: p.checksum, rol, repo: p.repositoryId }, `${rol} ${id}`);
|
||||||
|
pomuri(p.parentPom, 'parent-pom', id);
|
||||||
|
for (const d of p.dependencies || []) nod(d, ref, `${rol}-dependency`, id);
|
||||||
|
}
|
||||||
|
return lista.length;
|
||||||
|
};
|
||||||
|
const pluginuri = unelte(j.mavenPlugins, 'plugin', 'mavenPlugins'), extensii = unelte(j.mavenExtensions, 'extension', 'mavenExtensions');
|
||||||
|
for (const b of j.boms || []) pomuri(b, 'bom', 'the project');
|
||||||
|
if (j.pom && typeof j.pom === 'object') pomuri(j.pom.parent, 'parent-pom', 'the project pom');
|
||||||
|
for (const [, la, id] of muchii) if (!comp.has(la)) throw new Error(`lockfile.json: ${id} points to ${la}, which no included node has`);
|
||||||
|
const dependencies = graf(muchii.map(([de, la]) => [de, la]));
|
||||||
|
const components = [...comp.entries()].map(([purl, c]) => ({
|
||||||
|
type: 'library', 'bom-ref': purl, group: c.g, name: c.a, version: c.v, purl,
|
||||||
|
...(c.hash && MAVEN_HEX[c.hash.alg] ? { hashes: [{ alg: c.hash.alg, content: c.hash.sum }] } : {}),
|
||||||
|
properties: propr({
|
||||||
|
'aere:maven-role': [...c.roluri].sort().join(','), 'aere:maven-scope': c.scopes.size ? [...c.scopes].sort().join(',') : null,
|
||||||
|
'aere:maven-repository': c.repos.size ? [...c.repos].sort().join(',') : null,
|
||||||
|
'aere:maven-checksum': c.hash && !MAVEN_HEX[c.hash.alg] ? `${c.hash.alg}:${c.hash.sum}` : null,
|
||||||
|
'aere:maven-no-checksum': c.hash ? null : 'true', 'aere:maven-recorded': c.dinamic ? 'dynamic' : null,
|
||||||
|
}),
|
||||||
|
})).sort((a, b) => (a.purl < b.purl ? -1 : a.purl > b.purl ? 1 : 0));
|
||||||
|
// pom.xml COMIS fata de pom.checksum din lockfile: masurat, nu presupus
|
||||||
|
const p = citestePom(pomOcteti.toString('utf8'));
|
||||||
|
let potrivire = 'unrecorded';
|
||||||
|
if (j.pom && typeof j.pom === 'object' && str(j.pom.checksum)) {
|
||||||
|
const alg = MAVEN_NODE_HASH[j.pom.checksumAlgorithm];
|
||||||
|
potrivire = alg ? String(crypto.createHash(alg).update(pomOcteti).digest('hex') === String(j.pom.checksum).toLowerCase()) : `not comparable (${j.pom.checksumAlgorithm})`;
|
||||||
|
}
|
||||||
|
const cuLockfile = moduleAreLockfile ? p.modules.filter((m) => moduleAreLockfile(m) === true) : null;
|
||||||
|
const cfg = (j.metaData && j.metaData.config) || {}, env = (j.metaData && j.metaData.environment) || null;
|
||||||
|
const metadata = {
|
||||||
|
component: { type: 'application', 'bom-ref': root, group: gId, name: aId, version: ver, purl: root },
|
||||||
|
tools: { components: [{ type: 'application', name: 'aere-proof-of-software', version: TOOL.split('/')[1] }] },
|
||||||
|
properties: propr({
|
||||||
|
'aere:derived-from': 'lockfile.json', 'aere:maven-lockfile-version': j.lockFileVersion, 'aere:maven-lockfile-plugin': str(cfg.mavenLockfileVersion) ? cfg.mavenLockfileVersion : 'unrecorded',
|
||||||
|
'aere:maven-project-version': j.version, 'aere:maven-packaging': p.packaging || 'jar',
|
||||||
|
'aere:maven-checksum-mode': str(cfg.checksumMode) ? cfg.checksumMode : 'unrecorded', 'aere:maven-reduced': cfg.reduced == null ? 'unrecorded' : String(cfg.reduced),
|
||||||
|
'aere:maven-hermetic': cfg.hermetic == null ? null : String(cfg.hermetic),
|
||||||
|
'aere:maven-environment': env ? `${env.osName ?? '?'} / maven ${env.mavenVersion ?? '?'} / java ${env.javaVersion ?? '?'}` : 'not recorded',
|
||||||
|
'aere:maven-pom-matches-lockfile': potrivire,
|
||||||
|
'aere:maven-modules': p.modules.length ? p.modules.join(',') : 'none',
|
||||||
|
'aere:maven-modules-with-lockfile': p.modules.length ? (cuLockfile ? (cuLockfile.join(',') || 'none') : 'not checked') : null,
|
||||||
|
'aere:maven-plugins': pluginuri, 'aere:maven-extensions': extensii, 'aere:maven-dynamic': components.filter((c) => c.properties.some((x) => x.name === 'aere:maven-recorded')).length,
|
||||||
|
'aere:maven-omitted-for-conflict': omise.conflict, 'aere:maven-omitted-for-duplicate': omise.duplicate,
|
||||||
|
'aere:maven-without-checksum': components.filter((c) => c.properties.some((x) => x.name === 'aere:maven-no-checksum')).length,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
return { bomFormat: 'CycloneDX', specVersion: '1.6', serialNumber: serieDerivata({ metadata, dependencies, components }), version: 1, metadata, components, dependencies };
|
||||||
|
}
|
||||||
|
/** SBOM-ul Maven al arborelui COMIS <commit>:<cale>: lockfile.json si pom.xml din acelasi dosar; modulele se cauta in arbore. */
|
||||||
|
export function sbomMavenFromTree(repoDir, commit, rel, version) {
|
||||||
|
const lock = blobDin(repoDir, commit, rel, 'lockfile.json');
|
||||||
|
const pom = blobOctetiDin(repoDir, commit, rel, 'pom.xml');
|
||||||
|
if (lock == null) {
|
||||||
|
let module = []; try { module = pom ? citestePom(pom.toString('utf8')).modules : []; } catch { module = []; }
|
||||||
|
if (module.length) return { lipsa: `the committed tree has no lockfile.json; its pom.xml is an aggregator with modules ${module.join(', ')}, and the maven-lockfile plugin writes one lockfile.json per module: make the SBOM per module (--source-path <that module's directory>)` };
|
||||||
|
return { lipsa: 'the committed tree has no lockfile.json (written by the maven-lockfile plugin: mvn io.github.chains-project:maven-lockfile:generate), so the SBOM cannot be re-derived from it' };
|
||||||
|
}
|
||||||
|
if (pom == null) return { lipsa: 'the committed tree has lockfile.json but no pom.xml next to it (the file the lockfile was generated from, which also names the modules)' };
|
||||||
|
const sub = (m) => { const c = path.posix.normalize(path.posix.join(!rel || rel === '.' ? '' : rel, m)); return c.startsWith('..') ? false : existaIn(repoDir, commit, c, 'lockfile.json'); };
|
||||||
|
try { return { bom: sbomMaven({ lock, pom, version, moduleAreLockfile: sub }) }; }
|
||||||
|
catch (e) { return { lipsa: 'the committed lockfile.json or pom.xml cannot be read: ' + String(e.message || e).slice(0, 140) }; }
|
||||||
|
}
|
||||||
|
|
||||||
function sbomRebuildCheck(att, repoDir, sbomPath, ok, checks) {
|
function sbomRebuildCheck(att, repoDir, sbomPath, ok, checks) {
|
||||||
const s = att.statement.source;
|
const s = att.statement.source;
|
||||||
if (!att.statement.sbom) return;
|
if (!att.statement.sbom) return;
|
||||||
@ -557,8 +757,9 @@ function sbomRebuildCheck(att, repoDir, sbomPath, ok, checks) {
|
|||||||
// despre ARBORE: un fisier numit care nu e in arborele comis e o afirmatie FALSA; iar un SBOM care nu isi numeste fisierul, intr-un
|
// despre ARBORE: un fisier numit care nu e in arborele comis e o afirmatie FALSA; iar un SBOM care nu isi numeste fisierul, intr-un
|
||||||
// arbore fara package-lock.json, ramane nejudecat, dar motivul numeste fisierele din care arborele se POATE judeca.
|
// arbore fara package-lock.json, ramane nejudecat, dar motivul numeste fisierele din care arborele se POATE judeca.
|
||||||
const din = derivatDin(atestat), ver = att.statement.subject && att.statement.subject.version;
|
const din = derivatDin(atestat), ver = att.statement.subject && att.statement.subject.version;
|
||||||
const inArbore = (f) => { try { execFileSync('git', ['cat-file', '-e', `${s.commit}:${s.path}/${f}`], { cwd: repoDir, stdio: 'ignore' }); return true; } catch { return false; } };
|
const inArbore = (f) => existaIn(repoDir, s.commit, s.path, f);
|
||||||
const UNEALTA = { 'go.sum': 'sbom-go', 'packages.lock.json': 'sbom-nuget', 'gradle/verification-metadata.xml': 'sbom-gradle', 'package-lock.json': 'npm sbom' };
|
// 1.6.0: lockfile.json (pluginul maven-lockfile) se judeca cu sbom-maven
|
||||||
|
const UNEALTA = { 'go.sum': 'sbom-go', 'packages.lock.json': 'sbom-nuget', 'gradle/verification-metadata.xml': 'sbom-gradle', 'lockfile.json': 'sbom-maven', 'package-lock.json': 'npm sbom' };
|
||||||
const prezente = Object.keys(UNEALTA).filter(inArbore);
|
const prezente = Object.keys(UNEALTA).filter(inArbore);
|
||||||
if (din && !inArbore(din)) {
|
if (din && !inArbore(din)) {
|
||||||
return ok('rebuild: the file the attested SBOM says it was derived from is in the committed tree', false,
|
return ok('rebuild: the file the attested SBOM says it was derived from is in the committed tree', false,
|
||||||
@ -567,7 +768,7 @@ function sbomRebuildCheck(att, repoDir, sbomPath, ok, checks) {
|
|||||||
// un SBOM fara fisier numit (npm sbom, alte unelte) sau cu unul pe care verificatorul nu il stie se judeca fata de package-lock.json,
|
// un SBOM fara fisier numit (npm sbom, alte unelte) sau cu unul pe care verificatorul nu il stie se judeca fata de package-lock.json,
|
||||||
// ca pana acum; fara package-lock.json, dar cu alt fisier cunoscut in arbore, ramane nejudecat si motivul spune din ce se putea judeca
|
// ca pana acum; fara package-lock.json, dar cu alt fisier cunoscut in arbore, ramane nejudecat si motivul spune din ce se putea judeca
|
||||||
// (un nume necunoscut nu poate scoate un arbore npm de sub judecata: altfel oricare fisier al arborelui, numit, ar fi o scapare)
|
// (un nume necunoscut nu poate scoate un arbore npm de sub judecata: altfel oricare fisier al arborelui, numit, ar fi o scapare)
|
||||||
const FEL = { 'go.sum': 'go', 'packages.lock.json': 'nuget', 'gradle/verification-metadata.xml': 'gradle' };
|
const FEL = { 'go.sum': 'go', 'packages.lock.json': 'nuget', 'gradle/verification-metadata.xml': 'gradle', 'lockfile.json': 'maven' };
|
||||||
const fel = typeof din === 'string' && Object.hasOwn(FEL, din) ? FEL[din] : 'npm';
|
const fel = typeof din === 'string' && Object.hasOwn(FEL, din) ? FEL[din] : 'npm';
|
||||||
if (fel === 'npm' && !prezente.includes('package-lock.json') && prezente.length) {
|
if (fel === 'npm' && !prezente.includes('package-lock.json') && prezente.length) {
|
||||||
const cum = din ? `says it was derived from ${din}, which this verifier does not re-derive,` : 'names no lockfile (npm sbom and other tools do not)';
|
const cum = din ? `says it was derived from ${din}, which this verifier does not re-derive,` : 'names no lockfile (npm sbom and other tools do not)';
|
||||||
@ -577,7 +778,8 @@ function sbomRebuildCheck(att, repoDir, sbomPath, ok, checks) {
|
|||||||
const r = fel === 'go' ? sbomGoFromTree(repoDir, s.commit, s.path, ver)
|
const r = fel === 'go' ? sbomGoFromTree(repoDir, s.commit, s.path, ver)
|
||||||
: fel === 'nuget' ? sbomNugetFromTree(repoDir, s.commit, s.path, ver)
|
: fel === 'nuget' ? sbomNugetFromTree(repoDir, s.commit, s.path, ver)
|
||||||
: fel === 'gradle' ? sbomGradleFromTree(repoDir, s.commit, s.path, ver)
|
: fel === 'gradle' ? sbomGradleFromTree(repoDir, s.commit, s.path, ver)
|
||||||
: sbomNpmFromTree(repoDir, `${s.commit}:${s.path}`);
|
: fel === 'maven' ? sbomMavenFromTree(repoDir, s.commit, s.path, ver)
|
||||||
|
: sbomNpmFromTree(repoDir, caleGit(s.commit, s.path));
|
||||||
if (r.lipsa) { checks.push({ name: 'rebuild: SBOM', pass: null, detail: r.lipsa }); return; }
|
if (r.lipsa) { checks.push({ name: 'rebuild: SBOM', pass: null, detail: r.lipsa }); return; }
|
||||||
const a = sbomSemantica(atestat), b = sbomSemantica(r.bom);
|
const a = sbomSemantica(atestat), b = sbomSemantica(r.bom);
|
||||||
const aceleasi = JSON.stringify(a) === JSON.stringify(b);
|
const aceleasi = JSON.stringify(a) === JSON.stringify(b);
|
||||||
@ -587,7 +789,7 @@ function sbomRebuildCheck(att, repoDir, sbomPath, ok, checks) {
|
|||||||
const doarA = [...pa].filter((x) => !pb.has(x)).length, doarB = [...pb].filter((x) => !pa.has(x)).length;
|
const doarA = [...pa].filter((x) => !pb.has(x)).length, doarB = [...pb].filter((x) => !pa.has(x)).length;
|
||||||
detaliu = `${doarA} component(s) only in the attested SBOM, ${doarB} only in the lockfile; graph ${JSON.stringify(a.dependente) === JSON.stringify(b.dependente) ? 'same' : 'differs'}; root ${a.radacina === b.radacina ? 'same' : 'differs'}`;
|
detaliu = `${doarA} component(s) only in the attested SBOM, ${doarB} only in the lockfile; graph ${JSON.stringify(a.dependente) === JSON.stringify(b.dependente) ? 'same' : 'differs'}; root ${a.radacina === b.radacina ? 'same' : 'differs'}`;
|
||||||
}
|
}
|
||||||
const spune = { go: 'go.sum pins', nuget: 'packages.lock.json resolves', gradle: 'Gradle verification metadata pins' }[fel] || 'lockfile says';
|
const spune = { go: 'go.sum pins', nuget: 'packages.lock.json resolves', gradle: 'Gradle verification metadata pins', maven: 'lockfile.json (maven-lockfile) resolves' }[fel] || 'lockfile says';
|
||||||
ok(`rebuild: the attested SBOM says what the committed ${spune} (${b.componente.length} components)`, aceleasi, detaliu);
|
ok(`rebuild: the attested SBOM says what the committed ${spune} (${b.componente.length} components)`, aceleasi, detaliu);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -597,7 +799,7 @@ function sbomRebuildCheck(att, repoDir, sbomPath, ok, checks) {
|
|||||||
function rebuildChecks(att, repoDir, ok, note, sbomPath = null, checks = []) {
|
function rebuildChecks(att, repoDir, ok, note, sbomPath = null, checks = []) {
|
||||||
const s = att.statement.source, b = att.statement.build;
|
const s = att.statement.source, b = att.statement.build;
|
||||||
if (!s || !s.tree || !s.path) return ok('rebuild: the statement names a source tree', false, 'attested without --source-path');
|
if (!s || !s.tree || !s.path) return ok('rebuild: the statement names a source tree', false, 'attested without --source-path');
|
||||||
const tree = git(repoDir, ['rev-parse', '--verify', '--quiet', `${s.commit}:${s.path}`]);
|
const tree = git(repoDir, ['rev-parse', '--verify', '--quiet', caleGit(s.commit, s.path)]);
|
||||||
if (!ok(`source: ${s.commit.slice(0, 12)}:${s.path} is the attested tree ${s.tree.slice(0, 12)}`, tree === s.tree, tree ? `this clone has ${tree.slice(0, 12)}` : 'commit or path not in this clone')) return false;
|
if (!ok(`source: ${s.commit.slice(0, 12)}:${s.path} is the attested tree ${s.tree.slice(0, 12)}`, tree === s.tree, tree ? `this clone has ${tree.slice(0, 12)}` : 'commit or path not in this clone')) return false;
|
||||||
// 1.4.0: o atestare fara `npm pack` (un modul Go, un binar construit altfel) nu se poate reconstrui aici, si asta e ABSENT, nu fals;
|
// 1.4.0: o atestare fara `npm pack` (un modul Go, un binar construit altfel) nu se poate reconstrui aici, si asta e ABSENT, nu fals;
|
||||||
// arborele si SBOM-ul se judeca oricum (pana la 1.3.0 raspunsul era "fals" si SBOM-ul nu se mai judeca deloc)
|
// arborele si SBOM-ul se judeca oricum (pana la 1.3.0 raspunsul era "fals" si SBOM-ul nu se mai judeca deloc)
|
||||||
@ -608,7 +810,7 @@ function rebuildChecks(att, repoDir, ok, note, sbomPath = null, checks = []) {
|
|||||||
}
|
}
|
||||||
const out = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-rebuild-'));
|
const out = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-rebuild-'));
|
||||||
try {
|
try {
|
||||||
const tgz = packNpmFromTree(repoDir, `${s.commit}:${s.path}`, out);
|
const tgz = packNpmFromTree(repoDir, caleGit(s.commit, s.path), out);
|
||||||
const a = att.statement.artifacts.find((x) => x.name === path.basename(tgz));
|
const a = att.statement.artifacts.find((x) => x.name === path.basename(tgz));
|
||||||
if (!a) return ok(`rebuild: ${path.basename(tgz)} is an attested artifact`, false, 'the rebuilt file name is not in the statement');
|
if (!a) return ok(`rebuild: ${path.basename(tgz)} is an attested artifact`, false, 'the rebuilt file name is not in the statement');
|
||||||
const h = sha256File(tgz); const npmNow = npmVersion();
|
const h = sha256File(tgz); const npmNow = npmVersion();
|
||||||
@ -736,8 +938,9 @@ async function main() {
|
|||||||
const sourcePath = arg(args, '--source-path'); const outDir = arg(args, '--out-dir', '.'); const commit = arg(args, '--commit', 'HEAD');
|
const sourcePath = arg(args, '--source-path'); const outDir = arg(args, '--out-dir', '.'); const commit = arg(args, '--commit', 'HEAD');
|
||||||
if (!sourcePath) throw new Error('pack-npm needs --source-path <package directory>');
|
if (!sourcePath) throw new Error('pack-npm needs --source-path <package directory>');
|
||||||
const top = git(process.cwd(), ['rev-parse', '--show-toplevel']); if (!top) throw new Error('pack-npm needs a git checkout');
|
const top = git(process.cwd(), ['rev-parse', '--show-toplevel']); if (!top) throw new Error('pack-npm needs a git checkout');
|
||||||
const rel = path.relative(path.resolve(top), path.resolve(sourcePath)).split(path.sep).join('/');
|
const rel = path.relative(path.resolve(top), path.resolve(sourcePath)).split(path.sep).join('/') || '.';
|
||||||
console.log(packNpmFromTree(process.cwd(), `${commit}:${rel}`, outDir));
|
if (rel.startsWith('..')) throw new Error(`--source-path ${sourcePath}: must be the repository root or a directory below it`);
|
||||||
|
console.log(packNpmFromTree(process.cwd(), caleGit(commit, rel), outDir));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (cmd === 'keygen') {
|
if (cmd === 'keygen') {
|
||||||
@ -800,8 +1003,8 @@ async function main() {
|
|||||||
const commit = arg(args, '--commit', 'HEAD');
|
const commit = arg(args, '--commit', 'HEAD');
|
||||||
if (!src) throw new Error('sbom-go needs --source-path <directory of go.mod>');
|
if (!src) throw new Error('sbom-go needs --source-path <directory of go.mod>');
|
||||||
const top = git(process.cwd(), ['rev-parse', '--show-toplevel']); if (!top) throw new Error('sbom-go needs a git checkout');
|
const top = git(process.cwd(), ['rev-parse', '--show-toplevel']); if (!top) throw new Error('sbom-go needs a git checkout');
|
||||||
const rel = path.relative(path.resolve(top), path.resolve(src)).split(path.sep).join('/');
|
const rel = path.relative(path.resolve(top), path.resolve(src)).split(path.sep).join('/') || '.';
|
||||||
if (!rel || rel.startsWith('..')) throw new Error(`--source-path ${src}: must be a directory below the repository root`);
|
if (rel.startsWith('..')) throw new Error(`--source-path ${src}: must be the repository root or a directory below it`);
|
||||||
const r = sbomGoFromTree(top, git(top, ['rev-parse', commit]), rel, version);
|
const r = sbomGoFromTree(top, git(top, ['rev-parse', commit]), rel, version);
|
||||||
if (r.lipsa) throw new Error(r.lipsa);
|
if (r.lipsa) throw new Error(r.lipsa);
|
||||||
fs.writeFileSync(out, JSON.stringify(r.bom, null, 1) + '\n');
|
fs.writeFileSync(out, JSON.stringify(r.bom, null, 1) + '\n');
|
||||||
@ -814,8 +1017,8 @@ async function main() {
|
|||||||
const commit = arg(args, '--commit', 'HEAD');
|
const commit = arg(args, '--commit', 'HEAD');
|
||||||
if (!src) throw new Error(`${cmd} needs --source-path <directory of ${cmd === 'sbom-nuget' ? 'packages.lock.json' : 'settings.gradle'}>`);
|
if (!src) throw new Error(`${cmd} needs --source-path <directory of ${cmd === 'sbom-nuget' ? 'packages.lock.json' : 'settings.gradle'}>`);
|
||||||
const top = git(process.cwd(), ['rev-parse', '--show-toplevel']); if (!top) throw new Error(`${cmd} needs a git checkout`);
|
const top = git(process.cwd(), ['rev-parse', '--show-toplevel']); if (!top) throw new Error(`${cmd} needs a git checkout`);
|
||||||
const rel = path.relative(path.resolve(top), path.resolve(src)).split(path.sep).join('/');
|
const rel = path.relative(path.resolve(top), path.resolve(src)).split(path.sep).join('/') || '.';
|
||||||
if (!rel || rel.startsWith('..')) throw new Error(`--source-path ${src}: must be a directory below the repository root`);
|
if (rel.startsWith('..')) throw new Error(`--source-path ${src}: must be the repository root or a directory below it`);
|
||||||
const r = (cmd === 'sbom-nuget' ? sbomNugetFromTree : sbomGradleFromTree)(top, git(top, ['rev-parse', commit]), rel, version);
|
const r = (cmd === 'sbom-nuget' ? sbomNugetFromTree : sbomGradleFromTree)(top, git(top, ['rev-parse', commit]), rel, version);
|
||||||
if (r.lipsa) throw new Error(r.lipsa);
|
if (r.lipsa) throw new Error(r.lipsa);
|
||||||
fs.writeFileSync(out, JSON.stringify(r.bom, null, 1) + '\n');
|
fs.writeFileSync(out, JSON.stringify(r.bom, null, 1) + '\n');
|
||||||
@ -823,6 +1026,22 @@ async function main() {
|
|||||||
console.log(`SBOM written to ${out}: ${cu} package(s)${cmd === 'sbom-nuget' ? `, ${r.bom.components.length - cu} project reference(s), ${r.bom.dependencies.length} dependency record(s)` : ', no dependency graph (the Gradle lock files do not record it)'}`);
|
console.log(`SBOM written to ${out}: ${cu} package(s)${cmd === 'sbom-nuget' ? `, ${r.bom.components.length - cu} project reference(s), ${r.bom.dependencies.length} dependency record(s)` : ', no dependency graph (the Gradle lock files do not record it)'}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (cmd === 'sbom-maven') {
|
||||||
|
// 1.6.0: din arborele COMIS, ca sbom-go; --version e optional, lockfile.json poarta versiunea proiectului
|
||||||
|
const src = arg(args, '--source-path'); const out = arg(args, '--out', 'sbom.cdx.json'); const version = arg(args, '--version');
|
||||||
|
const commit = arg(args, '--commit', 'HEAD');
|
||||||
|
if (!src) throw new Error('sbom-maven needs --source-path <directory of lockfile.json and pom.xml> (. for the repository root)');
|
||||||
|
const top = git(process.cwd(), ['rev-parse', '--show-toplevel']); if (!top) throw new Error('sbom-maven needs a git checkout');
|
||||||
|
const rel = path.relative(path.resolve(top), path.resolve(src)).split(path.sep).join('/') || '.';
|
||||||
|
if (rel.startsWith('..')) throw new Error(`--source-path ${src}: must be the repository root or a directory below it`);
|
||||||
|
const r = sbomMavenFromTree(top, git(top, ['rev-parse', commit]), rel, version);
|
||||||
|
if (r.lipsa) throw new Error(r.lipsa);
|
||||||
|
fs.writeFileSync(out, JSON.stringify(r.bom, null, 1) + '\n');
|
||||||
|
const rol = (x) => r.bom.components.filter((c) => ((c.properties.find((q) => q.name === 'aere:maven-role') || {}).value || '').split(',').includes(x)).length;
|
||||||
|
const m = (n) => (r.bom.metadata.properties.find((q) => q.name === n) || {}).value;
|
||||||
|
console.log(`SBOM written to ${out}: ${rol('dependency')} dependency artifact(s), ${rol('plugin')} plugin(s) with ${rol('plugin-dependency')} plugin dependencies, ${rol('parent-pom') + rol('bom')} POM(s)/BOM(s); ${r.bom.components.filter((c) => c.hashes).length} of ${r.bom.components.length} components with a file hash; ${r.bom.dependencies.length} dependency record(s); ${m('aere:maven-omitted-for-conflict')} node(s) omitted for conflict, ${m('aere:maven-omitted-for-duplicate')} for duplicate; pom.xml matches the lockfile: ${m('aere:maven-pom-matches-lockfile')}; modules: ${m('aere:maven-modules')}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (cmd === 'model-bom') {
|
if (cmd === 'model-bom') {
|
||||||
const dir = arg(args, '--model-dir'); const out = arg(args, '--out', 'mlbom.json');
|
const dir = arg(args, '--model-dir'); const out = arg(args, '--out', 'mlbom.json');
|
||||||
const name = arg(args, '--name'); const version = arg(args, '--version'); const task = arg(args, '--task');
|
const name = arg(args, '--name'); const version = arg(args, '--version'); const task = arg(args, '--task');
|
||||||
@ -834,7 +1053,7 @@ async function main() {
|
|||||||
console.log('attest it with: node pos.mjs attest --base ' + dir + ' --sbom ' + out + ' --out attestation.json <the ' + r.files.length + ' files>');
|
console.log('attest it with: node pos.mjs attest --base ' + dir + ' --sbom ' + out + ' --out attestation.json <the ' + r.files.length + ' files>');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log('usage: pos.mjs keygen|pack-npm|sbom-go|sbom-nuget|sbom-gradle|attest|verify|model-bom ... (see README.md)');
|
console.log('usage: pos.mjs keygen|pack-npm|sbom-go|sbom-nuget|sbom-gradle|sbom-maven|attest|verify|model-bom ... (see README.md)');
|
||||||
process.exitCode = 2;
|
process.exitCode = 2;
|
||||||
}
|
}
|
||||||
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) main().catch((e) => { console.error('error:', e.message || e); process.exitCode = 1; });
|
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) main().catch((e) => { console.error('error:', e.message || e); process.exitCode = 1; });
|
||||||
|
|||||||
69
tools/proof-of-software/test/fixturi-maven/README.md
Normal file
69
tools/proof-of-software/test/fixturi-maven/README.md
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# Fixturi Maven (Proof of Software 1.6.0, `sbom-maven`)
|
||||||
|
|
||||||
|
Fisiere REALE scrise de pluginul maven-lockfile (`io.github.chains-project:maven-lockfile`), extrase cu `git show` (octetii COMISI, fara
|
||||||
|
conversia de checkout) din depozitul public https://github.com/chains-project/maven-lockfile la commitul
|
||||||
|
`083b879a2202189aea2b29b47c52ef649b5e4289` (2026-10-16T23:15:34+02:00), pe 2026-10-18. Clona sta in afara depozitului nostru, numai de
|
||||||
|
citit; nimic nu a rulat din ea (nici `mvn`, nici alt interpretor). Amprentele de mai jos sunt ale octetilor extrasi si sunt identice cu ale
|
||||||
|
bloburilor din git (verificat cu `git show ... | sha256sum`). Dosarul e marcat `-text` in `.gitattributes`, ca un checkout cu
|
||||||
|
`core.autocrlf=true` sa nu le schimbe octetii (proba compara SHA-256 al `pom.xml` de pe disc cu `pom.checksum` din lockfile).
|
||||||
|
|
||||||
|
| fixtura | calea in depozitul sursa | pluginul care a scris-o (`metaData.config`) | sha256 | octeti |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `maven-lockfile/lockfile.json` | `lockfile.json` (radacina: lockfile-ul propriu al pluginului) | 5.18.6, `checksumMode: remote`, `hermetic: true`, `includeBoms`, `includeParentPom` | `f94e6a78e9680d93089342dd5a922cf89508a7c921433ba75695e34ccc97acf1` | 1453087 |
|
||||||
|
| `maven-lockfile/pom.xml` | `pom.xml` | | `da144afab3256a91113eeca080f9b0836b7615c43327bdcdf40f2db1460a53ce` | 18006 |
|
||||||
|
| `reactor/pom.xml` | `src/test/resources-its/it/IntegrationTestsIT/reactorSnapshotSiblingShouldSucceed/pom.xml` (agregator, `<modules>`) | | `2c5e3529a91fa1289e348d7e7dbb7d2bcea6998882b163c474382a75f4b3daca` | 516 |
|
||||||
|
| `reactor/module-a/pom.xml` | `.../reactorSnapshotSiblingShouldSucceed/module-a/pom.xml` | | `fe5fd57c834ef8943fc6903fe2343708c920ee9e796de245c92e880bfe42be2b` | 1028 |
|
||||||
|
| `reactor/module-a/lockfile.json` | `.../reactorSnapshotSiblingShouldSucceed/module-a/lockfile.json` (dependinta de modul-frate din acelasi reactor, checksum gol) | 5.17.1-SNAPSHOT, `local` | `41de249b2fc9c6fc18777f2b0d589e6462da07040590806c5a17ba3efabcb233` | 2007 |
|
||||||
|
| `reactor/module-b/pom.xml` | `.../reactorSnapshotSiblingShouldSucceed/module-b/pom.xml` (fara lockfile comis) | | `ff80de0e2f3e3f4cb108ba102e51e927f33ee4bf7c57de7de9e9fa814355b32c` | 288 |
|
||||||
|
| `classifier/lockfile.json` | `.../classifierDependencyCheckCorrect/lockfile.json` (acelasi artefact simplu, `sources` si `javadoc`) | 5.14.3-SNAPSHOT, `local` | `b21155db575cdeca577889e8ed18421d0f7b13041ada4f7ad4a57b9aade3a672` | 11527 |
|
||||||
|
| `classifier/pom.xml` | `.../classifierDependencyCheckCorrect/pom.xml` | | `ffb0ec623d40e3b9655330aa563e8c17ca14b4e33bf3f1ac3803c6a02fdd3dee` | 1506 |
|
||||||
|
|
||||||
|
Ce contine fixtura mare, numarat cu o parcurgere INDEPENDENTA a lockfile-ului (in `test/sbom-maven.test.mjs`, alt cod decat `pos.mjs`):
|
||||||
|
986 de noduri in arborele proiectului si in cei 17 arbori de plugin; 325 de artefacte distincte (318 incluse + 7 inregistrate dinamic in
|
||||||
|
modul hermetic, furnizorii surefire, scrise de plugin cu `included: false` si FARA `selectedVersion`); 528 de noduri scoase de mediere
|
||||||
|
(159 pentru conflict, 369 pentru duplicat); 2 cu clasificator (`guice:classes`, `sisu-guice:noaop`); 2 cu `type: pom`; 17 pluginuri; 232 de
|
||||||
|
pom-uri parinte si 43 de BOM-uri; 617 componente in SBOM, toate cu SHA-256; 4 scope-uri (compile, provided, runtime, test); 13 coordonate
|
||||||
|
care apar si in arborele proiectului si intr-un arbore de plugin; 23 cu doua scope-uri.
|
||||||
|
|
||||||
|
## Semantica citita din sursa pluginului (nu din memorie)
|
||||||
|
|
||||||
|
`src/main/java/io/github/chains_project/maven_lockfile/graph/DependencyGraph.java`, `LockFileFacade.java`, `data/*.java`, la acelasi commit:
|
||||||
|
|
||||||
|
- `included = winnerVersion.isEmpty()` (nu exista marcaj de conflict in arborele verbose al lui Maven); `selectedVersion` = versiunea
|
||||||
|
castigatoare cand exista marcaj, altfel chiar versiunea nodului. Deci `included: false` cu `selectedVersion == version` e "omis pentru
|
||||||
|
duplicat", cu alta versiune e "omis pentru conflict"; niciunul nu ajunge pe classpath, parintele primeste versiunea aleasa.
|
||||||
|
- artefactele inregistrate dinamic (`addDynamicDependency`, modul hermetic) se construiesc cu `DependencyNode.of(...)` fara
|
||||||
|
`setIncluded`/`setSelectedVersion`: in JSON `included: false` si fara `selectedVersion`. Sunt rezolvate, nu mediate.
|
||||||
|
- `ArtifactType.of("jar")` si `Classifier.of("")` dau null, deci `type` si `classifier` lipsesc din JSON cand sunt jar, respectiv goale.
|
||||||
|
- checksum-ul e gol pentru radacina si pentru modulele SNAPSHOT din acelasi reactor (`reactorGavs`), altfel e hash-ul fisierului:
|
||||||
|
`remote` il citeste din depozitul de artefacte, `local` il calculeaza pe fisier (`FileSystemChecksumCalculator`, implicit SHA-256).
|
||||||
|
- `pom.checksum` e hash-ul fisierului `pom.xml` din care s-a generat lockfile-ul.
|
||||||
|
|
||||||
|
## Controale pozitive ale metodei, masurate 2026-10-18
|
||||||
|
|
||||||
|
- `pom.checksum` din `maven-lockfile/lockfile.json` == SHA-256 al `pom.xml` COMIS (`da144afa...`): deci `aere:maven-pom-matches-lockfile`
|
||||||
|
masoara ceva real. Proba o recalculeaza in test, independent de `pos.mjs`.
|
||||||
|
- checksum-ul din lockfile == sidecar-ul `.sha256` publicat de Maven Central (citit ca text, nu descarcat): `gson-2.14.0.jar`
|
||||||
|
`2cbd119bf1961c28788310963dc80ba65f58cdeec1dd139c8bdb1240faa2c36f`, `guava-33.5.0-jre.jar`
|
||||||
|
`1e301f0c52ac248b0b14fdc3d12283c77252d4d6f48521d572e7d8c4c2cc4ac7`, `gson-parent-2.14.0.pom`
|
||||||
|
`82b0f6694c5758ab8bb07e05630208d50f589d4b5e04fd0a046d59cdf7ba3d1d`. Deci checksum-ul e hash-ul FISIERULUI si intra in `hashes` CycloneDX.
|
||||||
|
- Masina de lucru nu are `~/.m2/repository`, deci proba cu jar-urile din memoria locala Maven e SARITA aici si ruleaza unde exista.
|
||||||
|
|
||||||
|
## Dogfood pe depozitul public, 2026-10-18
|
||||||
|
|
||||||
|
Pe clona 1 (`083b879a`, checkout superficial) si pe o a DOUA clona proaspata a aceluiasi depozit, FARA checkout (numai obiecte git, 44 MB),
|
||||||
|
cu `sbomMavenFromTree` / `attest` / `verify` din `pos.mjs` chemate dintr-un script din afara clonelor (in clone a intrat numai git):
|
||||||
|
|
||||||
|
- SBOM din clona 1, `--source-path .` (lockfile-ul e la radacina depozitului): 617 componente (71 dependinte, 17 pluginuri, 267 dependinte de
|
||||||
|
plugin, 232 pom-uri parinte, 43 BOM-uri), 617 cu SHA-256, 218 inregistrari de graf, 4 scope-uri, 159/369 noduri omise pentru
|
||||||
|
conflict/duplicat, 7 dinamice, `pom.xml matches lockfile: true`, 452.104 octeti; 131 ms (a doua rulare 119 ms, aceiasi octeti).
|
||||||
|
- Din clona 2: aceiasi octeti, 117 ms. Serialul `urn:uuid:125f2369-0208-518b-84a9-37538ef0fd9a` e derivat din continut.
|
||||||
|
- Atestare cu `source.path "."`, arborele `79b9c003543c`; `verify --rebuild-from <clona 2>`: VALID in 487 ms ("the attested SBOM says what the
|
||||||
|
committed lockfile.json (maven-lockfile) resolves (617 components)"); artefactul e un stand-in declarat (nu a rulat niciun build Maven),
|
||||||
|
deci "rebuild: artifacts" e ABSENT, nu fals.
|
||||||
|
- Control negativ: componentele numite `gson` scoase de mana din SBOM-ul atestat (sunt doua: 2.13.2 si 2.14.0): VALID pe digest, INVALID
|
||||||
|
la reconstructie ("0 component(s) only in the attested SBOM, 2 only in the lockfile; graph same; root same").
|
||||||
|
|
||||||
|
NEMASURAT: un lockfile scris cu `reduced: true` (fara noduri mediate) si unul cu `mavenExtensions` nevid; cititorul le accepta dupa
|
||||||
|
aceeasi schema, dar nicio fixtura reala de aici nu le are. Niciun alt depozit public decat al pluginului nu a fost gasit cu `lockfile.json`
|
||||||
|
comis (cautare web, 2026-10-18), deci un lockfile scris de alt proiect, cu alte optiuni, nu a fost citit.
|
||||||
@ -0,0 +1,286 @@
|
|||||||
|
{
|
||||||
|
"artifactId": "classifier-dependency-check-correct",
|
||||||
|
"groupId": "com.mycompany.app",
|
||||||
|
"version": "1",
|
||||||
|
"pom": {
|
||||||
|
"groupId": "com.mycompany.app",
|
||||||
|
"artifactId": "classifier-dependency-check-correct",
|
||||||
|
"version": "1",
|
||||||
|
"relativePath": "pom.xml",
|
||||||
|
"checksumAlgorithm": "SHA-256",
|
||||||
|
"checksum": "ffb0ec623d40e3b9655330aa563e8c17ca14b4e33bf3f1ac3803c6a02fdd3dee"
|
||||||
|
},
|
||||||
|
"lockFileVersion": 1,
|
||||||
|
"dependencies": [
|
||||||
|
{
|
||||||
|
"groupId": "org.junit.jupiter",
|
||||||
|
"artifactId": "junit-jupiter-api",
|
||||||
|
"version": "5.9.2",
|
||||||
|
"classifier": "sources",
|
||||||
|
"checksumAlgorithm": "SHA-256",
|
||||||
|
"checksum": "2b04279c000da27679100d5854d3045a09c2a9a4cda942777f0b0519bb9f295d",
|
||||||
|
"scope": "compile",
|
||||||
|
"resolved": "https://repo.maven.apache.org/maven2/org/junit/jupiter/junit-jupiter-api/5.9.2/junit-jupiter-api-5.9.2-sources.jar",
|
||||||
|
"repositoryId": "central",
|
||||||
|
"selectedVersion": "5.9.2",
|
||||||
|
"included": true,
|
||||||
|
"id": "org.junit.jupiter:junit-jupiter-api:5.9.2",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"groupId": "org.apiguardian",
|
||||||
|
"artifactId": "apiguardian-api",
|
||||||
|
"version": "1.1.2",
|
||||||
|
"checksumAlgorithm": "SHA-256",
|
||||||
|
"checksum": "b509448ac506d607319f182537f0b35d71007582ec741832a1f111e5b5b70b38",
|
||||||
|
"scope": "compile",
|
||||||
|
"resolved": "https://repo.maven.apache.org/maven2/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar",
|
||||||
|
"repositoryId": "central",
|
||||||
|
"selectedVersion": "1.1.2",
|
||||||
|
"included": false,
|
||||||
|
"id": "org.apiguardian:apiguardian-api:1.1.2",
|
||||||
|
"parent": "org.junit.jupiter:junit-jupiter-api:5.9.2",
|
||||||
|
"children": [],
|
||||||
|
"boms": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"groupId": "org.junit.platform",
|
||||||
|
"artifactId": "junit-platform-commons",
|
||||||
|
"version": "1.9.2",
|
||||||
|
"checksumAlgorithm": "SHA-256",
|
||||||
|
"checksum": "624a3d745ef1d28e955a6a67af8edba0fdfc5c9bad680a73f67a70bb950a683d",
|
||||||
|
"scope": "compile",
|
||||||
|
"resolved": "https://repo.maven.apache.org/maven2/org/junit/platform/junit-platform-commons/1.9.2/junit-platform-commons-1.9.2.jar",
|
||||||
|
"repositoryId": "central",
|
||||||
|
"selectedVersion": "1.9.2",
|
||||||
|
"included": false,
|
||||||
|
"id": "org.junit.platform:junit-platform-commons:1.9.2",
|
||||||
|
"parent": "org.junit.jupiter:junit-jupiter-api:5.9.2",
|
||||||
|
"children": [],
|
||||||
|
"boms": [
|
||||||
|
{
|
||||||
|
"groupId": "org.junit",
|
||||||
|
"artifactId": "junit-bom",
|
||||||
|
"version": "5.9.2",
|
||||||
|
"resolved": "https://repo.maven.apache.org/maven2/org/junit/junit-bom/5.9.2/junit-bom-5.9.2.pom",
|
||||||
|
"repositoryId": "central",
|
||||||
|
"checksumAlgorithm": "SHA-256",
|
||||||
|
"checksum": "2ed07d65845131f5336a86476c9a4056b59d0b58b9815ab3679bb0f36f35f705"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"groupId": "org.opentest4j",
|
||||||
|
"artifactId": "opentest4j",
|
||||||
|
"version": "1.2.0",
|
||||||
|
"checksumAlgorithm": "SHA-256",
|
||||||
|
"checksum": "58812de60898d976fb81ef3b62da05c6604c18fd4a249f5044282479fc286af2",
|
||||||
|
"scope": "compile",
|
||||||
|
"resolved": "https://repo.maven.apache.org/maven2/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar",
|
||||||
|
"repositoryId": "central",
|
||||||
|
"selectedVersion": "1.2.0",
|
||||||
|
"included": false,
|
||||||
|
"id": "org.opentest4j:opentest4j:1.2.0",
|
||||||
|
"parent": "org.junit.jupiter:junit-jupiter-api:5.9.2",
|
||||||
|
"children": [],
|
||||||
|
"boms": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"boms": [
|
||||||
|
{
|
||||||
|
"groupId": "org.junit",
|
||||||
|
"artifactId": "junit-bom",
|
||||||
|
"version": "5.9.2",
|
||||||
|
"resolved": "https://repo.maven.apache.org/maven2/org/junit/junit-bom/5.9.2/junit-bom-5.9.2.pom",
|
||||||
|
"repositoryId": "central",
|
||||||
|
"checksumAlgorithm": "SHA-256",
|
||||||
|
"checksum": "2ed07d65845131f5336a86476c9a4056b59d0b58b9815ab3679bb0f36f35f705"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"groupId": "org.junit.jupiter",
|
||||||
|
"artifactId": "junit-jupiter-api",
|
||||||
|
"version": "5.9.2",
|
||||||
|
"classifier": "javadoc",
|
||||||
|
"checksumAlgorithm": "SHA-256",
|
||||||
|
"checksum": "789224a3a7bff190858307399f64ee7d7e4ab810c7eab12ee107e27765acd8d9",
|
||||||
|
"scope": "compile",
|
||||||
|
"resolved": "https://repo.maven.apache.org/maven2/org/junit/jupiter/junit-jupiter-api/5.9.2/junit-jupiter-api-5.9.2-javadoc.jar",
|
||||||
|
"repositoryId": "central",
|
||||||
|
"selectedVersion": "5.9.2",
|
||||||
|
"included": true,
|
||||||
|
"id": "org.junit.jupiter:junit-jupiter-api:5.9.2",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"groupId": "org.apiguardian",
|
||||||
|
"artifactId": "apiguardian-api",
|
||||||
|
"version": "1.1.2",
|
||||||
|
"checksumAlgorithm": "SHA-256",
|
||||||
|
"checksum": "b509448ac506d607319f182537f0b35d71007582ec741832a1f111e5b5b70b38",
|
||||||
|
"scope": "compile",
|
||||||
|
"resolved": "https://repo.maven.apache.org/maven2/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar",
|
||||||
|
"repositoryId": "central",
|
||||||
|
"selectedVersion": "1.1.2",
|
||||||
|
"included": false,
|
||||||
|
"id": "org.apiguardian:apiguardian-api:1.1.2",
|
||||||
|
"parent": "org.junit.jupiter:junit-jupiter-api:5.9.2",
|
||||||
|
"children": [],
|
||||||
|
"boms": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"groupId": "org.junit.platform",
|
||||||
|
"artifactId": "junit-platform-commons",
|
||||||
|
"version": "1.9.2",
|
||||||
|
"checksumAlgorithm": "SHA-256",
|
||||||
|
"checksum": "624a3d745ef1d28e955a6a67af8edba0fdfc5c9bad680a73f67a70bb950a683d",
|
||||||
|
"scope": "compile",
|
||||||
|
"resolved": "https://repo.maven.apache.org/maven2/org/junit/platform/junit-platform-commons/1.9.2/junit-platform-commons-1.9.2.jar",
|
||||||
|
"repositoryId": "central",
|
||||||
|
"selectedVersion": "1.9.2",
|
||||||
|
"included": false,
|
||||||
|
"id": "org.junit.platform:junit-platform-commons:1.9.2",
|
||||||
|
"parent": "org.junit.jupiter:junit-jupiter-api:5.9.2",
|
||||||
|
"children": [],
|
||||||
|
"boms": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"groupId": "org.opentest4j",
|
||||||
|
"artifactId": "opentest4j",
|
||||||
|
"version": "1.2.0",
|
||||||
|
"checksumAlgorithm": "SHA-256",
|
||||||
|
"checksum": "58812de60898d976fb81ef3b62da05c6604c18fd4a249f5044282479fc286af2",
|
||||||
|
"scope": "compile",
|
||||||
|
"resolved": "https://repo.maven.apache.org/maven2/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar",
|
||||||
|
"repositoryId": "central",
|
||||||
|
"selectedVersion": "1.2.0",
|
||||||
|
"included": false,
|
||||||
|
"id": "org.opentest4j:opentest4j:1.2.0",
|
||||||
|
"parent": "org.junit.jupiter:junit-jupiter-api:5.9.2",
|
||||||
|
"children": [],
|
||||||
|
"boms": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"boms": [
|
||||||
|
{
|
||||||
|
"groupId": "org.junit",
|
||||||
|
"artifactId": "junit-bom",
|
||||||
|
"version": "5.9.2",
|
||||||
|
"resolved": "https://repo.maven.apache.org/maven2/org/junit/junit-bom/5.9.2/junit-bom-5.9.2.pom",
|
||||||
|
"repositoryId": "central",
|
||||||
|
"checksumAlgorithm": "SHA-256",
|
||||||
|
"checksum": "2ed07d65845131f5336a86476c9a4056b59d0b58b9815ab3679bb0f36f35f705"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"groupId": "org.junit.jupiter",
|
||||||
|
"artifactId": "junit-jupiter-api",
|
||||||
|
"version": "5.9.2",
|
||||||
|
"checksumAlgorithm": "SHA-256",
|
||||||
|
"checksum": "f767a170f97127b0ad3582bf3358eabbbbe981d9f96411853e629d9276926fd5",
|
||||||
|
"scope": "compile",
|
||||||
|
"resolved": "https://repo.maven.apache.org/maven2/org/junit/jupiter/junit-jupiter-api/5.9.2/junit-jupiter-api-5.9.2.jar",
|
||||||
|
"repositoryId": "central",
|
||||||
|
"selectedVersion": "5.9.2",
|
||||||
|
"included": true,
|
||||||
|
"id": "org.junit.jupiter:junit-jupiter-api:5.9.2",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"groupId": "org.apiguardian",
|
||||||
|
"artifactId": "apiguardian-api",
|
||||||
|
"version": "1.1.2",
|
||||||
|
"checksumAlgorithm": "SHA-256",
|
||||||
|
"checksum": "b509448ac506d607319f182537f0b35d71007582ec741832a1f111e5b5b70b38",
|
||||||
|
"scope": "compile",
|
||||||
|
"resolved": "https://repo.maven.apache.org/maven2/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar",
|
||||||
|
"repositoryId": "central",
|
||||||
|
"selectedVersion": "1.1.2",
|
||||||
|
"included": true,
|
||||||
|
"id": "org.apiguardian:apiguardian-api:1.1.2",
|
||||||
|
"parent": "org.junit.jupiter:junit-jupiter-api:5.9.2",
|
||||||
|
"children": [],
|
||||||
|
"boms": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"groupId": "org.junit.platform",
|
||||||
|
"artifactId": "junit-platform-commons",
|
||||||
|
"version": "1.9.2",
|
||||||
|
"checksumAlgorithm": "SHA-256",
|
||||||
|
"checksum": "624a3d745ef1d28e955a6a67af8edba0fdfc5c9bad680a73f67a70bb950a683d",
|
||||||
|
"scope": "compile",
|
||||||
|
"resolved": "https://repo.maven.apache.org/maven2/org/junit/platform/junit-platform-commons/1.9.2/junit-platform-commons-1.9.2.jar",
|
||||||
|
"repositoryId": "central",
|
||||||
|
"selectedVersion": "1.9.2",
|
||||||
|
"included": true,
|
||||||
|
"id": "org.junit.platform:junit-platform-commons:1.9.2",
|
||||||
|
"parent": "org.junit.jupiter:junit-jupiter-api:5.9.2",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"groupId": "org.apiguardian",
|
||||||
|
"artifactId": "apiguardian-api",
|
||||||
|
"version": "1.1.2",
|
||||||
|
"checksumAlgorithm": "SHA-256",
|
||||||
|
"checksum": "b509448ac506d607319f182537f0b35d71007582ec741832a1f111e5b5b70b38",
|
||||||
|
"scope": "compile",
|
||||||
|
"resolved": "https://repo.maven.apache.org/maven2/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar",
|
||||||
|
"repositoryId": "central",
|
||||||
|
"selectedVersion": "1.1.2",
|
||||||
|
"included": false,
|
||||||
|
"id": "org.apiguardian:apiguardian-api:1.1.2",
|
||||||
|
"parent": "org.junit.platform:junit-platform-commons:1.9.2",
|
||||||
|
"children": [],
|
||||||
|
"boms": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"boms": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"groupId": "org.opentest4j",
|
||||||
|
"artifactId": "opentest4j",
|
||||||
|
"version": "1.2.0",
|
||||||
|
"checksumAlgorithm": "SHA-256",
|
||||||
|
"checksum": "58812de60898d976fb81ef3b62da05c6604c18fd4a249f5044282479fc286af2",
|
||||||
|
"scope": "compile",
|
||||||
|
"resolved": "https://repo.maven.apache.org/maven2/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar",
|
||||||
|
"repositoryId": "central",
|
||||||
|
"selectedVersion": "1.2.0",
|
||||||
|
"included": true,
|
||||||
|
"id": "org.opentest4j:opentest4j:1.2.0",
|
||||||
|
"parent": "org.junit.jupiter:junit-jupiter-api:5.9.2",
|
||||||
|
"children": [],
|
||||||
|
"boms": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"boms": [
|
||||||
|
{
|
||||||
|
"groupId": "org.junit",
|
||||||
|
"artifactId": "junit-bom",
|
||||||
|
"version": "5.9.2",
|
||||||
|
"resolved": "https://repo.maven.apache.org/maven2/org/junit/junit-bom/5.9.2/junit-bom-5.9.2.pom",
|
||||||
|
"repositoryId": "central",
|
||||||
|
"checksumAlgorithm": "SHA-256",
|
||||||
|
"checksum": "2ed07d65845131f5336a86476c9a4056b59d0b58b9815ab3679bb0f36f35f705"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"mavenPlugins": [],
|
||||||
|
"metaData": {
|
||||||
|
"environment": {
|
||||||
|
"osName": "Linux",
|
||||||
|
"mavenVersion": "3.9.9",
|
||||||
|
"javaVersion": "21.0.9"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"includeMavenPlugins": false,
|
||||||
|
"allowValidationFailure": false,
|
||||||
|
"allowPomValidationFailure": true,
|
||||||
|
"allowEnvironmentalValidationFailure": true,
|
||||||
|
"includeEnvironment": true,
|
||||||
|
"reduced": false,
|
||||||
|
"mavenLockfileVersion": "5.14.3-SNAPSHOT",
|
||||||
|
"checksumMode": "local",
|
||||||
|
"checksumAlgorithm": "SHA-256"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
<project>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>com.mycompany.app</groupId>
|
||||||
|
<artifactId>classifier-dependency-check-correct</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<version>1</version>
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.target>11</maven.compiler.target>
|
||||||
|
<maven.compiler.source>11</maven.compiler.source>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-api</artifactId>
|
||||||
|
<version>5.9.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-api</artifactId>
|
||||||
|
<version>5.9.2</version>
|
||||||
|
<classifier>sources</classifier>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-api</artifactId>
|
||||||
|
<version>5.9.2</version>
|
||||||
|
<classifier>javadoc</classifier>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>io.github.chains-project</groupId>
|
||||||
|
<artifactId>
|
||||||
|
maven-lockfile</artifactId>
|
||||||
|
<version>@project.version@</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>validate</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<configuration>
|
||||||
|
<checksumMode>local</checksumMode>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
28873
tools/proof-of-software/test/fixturi-maven/maven-lockfile/lockfile.json
Normal file
28873
tools/proof-of-software/test/fixturi-maven/maven-lockfile/lockfile.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,519 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>io.github.chains-project</groupId>
|
||||||
|
<artifactId>maven-lockfile</artifactId>
|
||||||
|
<version>5.18.7-SNAPSHOT</version>
|
||||||
|
<packaging>maven-plugin</packaging>
|
||||||
|
<name>maven-lockfile-plugin</name>
|
||||||
|
<description>This plugin is a state-of-the-art solution that can be used to validate the integrity
|
||||||
|
of a maven repository.
|
||||||
|
It does this by generating a lock file that contains the checksums of all the artifacts and plugins in the
|
||||||
|
repository.
|
||||||
|
The lock file can then be used to validate the integrity of the repository.
|
||||||
|
This guards the supply chain against malicious actors that might tamper with the artifacts in
|
||||||
|
the repository.</description>
|
||||||
|
<url>https://github.com/chains-project/maven-lockfile</url>
|
||||||
|
<inceptionYear>2023</inceptionYear>
|
||||||
|
<licenses>
|
||||||
|
<license>
|
||||||
|
<name>MIT</name>
|
||||||
|
<url>https://opensource.org/licenses/MIT</url>
|
||||||
|
<distribution>repo</distribution>
|
||||||
|
</license>
|
||||||
|
</licenses>
|
||||||
|
<developers>
|
||||||
|
<developer>
|
||||||
|
<id>MartinWitt</id>
|
||||||
|
<name>Martin Wittlinger</name>
|
||||||
|
</developer>
|
||||||
|
</developers>
|
||||||
|
<prerequisites>
|
||||||
|
<maven>3.2.5</maven>
|
||||||
|
</prerequisites>
|
||||||
|
<scm>
|
||||||
|
<connection>scm:git:https://github.com/chains-project/maven-lockfile</connection>
|
||||||
|
<developerConnection>scm:git:https://github.com/chains-project/maven-lockfile.git</developerConnection>
|
||||||
|
<tag>v${project.version}</tag>
|
||||||
|
<url>https://github.com/chains-project/maven-lockfile/tree/${project.scm.tag}</url>
|
||||||
|
</scm>
|
||||||
|
<properties>
|
||||||
|
<mavenVersion>3.9.11</mavenVersion>
|
||||||
|
<maven.compiler.release>11</maven.compiler.release>
|
||||||
|
<project.build.outputTimestamp>2026-10-15T22:53:04Z</project.build.outputTimestamp>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<sigstore.skip>true</sigstore.skip>
|
||||||
|
</properties>
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<!-- Force upgrade of vulnerable transitive plexus-utils (GHSA-6fmv-xxpf-w3cw / CVE-2025-67030).
|
||||||
|
maven-core pulls in 3.6.0 via maven-model; override to safe version 4.0.3. -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.codehaus.plexus</groupId>
|
||||||
|
<artifactId>plexus-utils</artifactId>
|
||||||
|
<version>4.1.0</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.maven</groupId>
|
||||||
|
<artifactId>maven-plugin-api</artifactId>
|
||||||
|
<version>${mavenVersion}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<!-- plexus-utils 4.x extracted XML APIs into plexus-xml; needed for XmlPullParserException -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.codehaus.plexus</groupId>
|
||||||
|
<artifactId>plexus-xml</artifactId>
|
||||||
|
<version>4.2.0</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.maven.plugin-tools</groupId>
|
||||||
|
<artifactId>maven-plugin-annotations</artifactId>
|
||||||
|
<version>3.16.0</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-api</artifactId>
|
||||||
|
<version>6.1.3</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.maven</groupId>
|
||||||
|
<artifactId>maven-core</artifactId>
|
||||||
|
<version>${mavenVersion}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.code.gson</groupId>
|
||||||
|
<artifactId>gson</artifactId>
|
||||||
|
<version>2.14.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.maven.resolver</groupId>
|
||||||
|
<artifactId>maven-resolver-api</artifactId>
|
||||||
|
<version>2.0.24</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.maven.plugin-testing</groupId>
|
||||||
|
<artifactId>maven-plugin-testing-harness</artifactId>
|
||||||
|
<version>4.0.0-beta-4</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.maven</groupId>
|
||||||
|
<artifactId>maven-compat</artifactId>
|
||||||
|
<version>3.9.6</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
|
<version>6.1.3</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<!-- Explicit dependency to fix GHSA-rqfh-9r24-8c9r (XXE vulnerability in versions < 3.27.7) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.assertj</groupId>
|
||||||
|
<artifactId>assertj-core</artifactId>
|
||||||
|
<version>3.27.7</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
|
<artifactId>log4j-core</artifactId>
|
||||||
|
<version>2.26.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.soebes.itf.jupiter.extension</groupId>
|
||||||
|
<artifactId>itf-jupiter-extension</artifactId>
|
||||||
|
<version>0.13.1</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.soebes.itf.jupiter.extension</groupId>
|
||||||
|
<artifactId>itf-assertj</artifactId>
|
||||||
|
<version>0.13.1</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.instancio</groupId>
|
||||||
|
<artifactId>instancio-junit</artifactId>
|
||||||
|
<version>5.6.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.guava</groupId>
|
||||||
|
<artifactId>guava</artifactId>
|
||||||
|
<version>33.5.0-jre</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.maven.shared</groupId>
|
||||||
|
<artifactId>maven-dependency-tree</artifactId>
|
||||||
|
<version>3.3.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.maven.shared</groupId>
|
||||||
|
<artifactId>maven-artifact-transfer</artifactId>
|
||||||
|
<version>0.13.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.maven</groupId>
|
||||||
|
<artifactId>maven-model</artifactId>
|
||||||
|
<version>${mavenVersion}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.maven</groupId>
|
||||||
|
<artifactId>maven-artifact</artifactId>
|
||||||
|
<version>${mavenVersion}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-io</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
<version>2.22.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.cyclonedx</groupId>
|
||||||
|
<artifactId>cyclonedx-maven-plugin</artifactId>
|
||||||
|
<version>2.9.3</version>
|
||||||
|
<inherited>true</inherited>
|
||||||
|
<configuration>
|
||||||
|
<includeTestScope>true</includeTestScope>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>makeBom</goal>
|
||||||
|
</goals>
|
||||||
|
<phase>package</phase>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>dev.sigstore</groupId>
|
||||||
|
<artifactId>sigstore-maven-plugin</artifactId>
|
||||||
|
<version>2.0.0</version>
|
||||||
|
<configuration>
|
||||||
|
<skip>${sigstore.skip}</skip>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>sign</id>
|
||||||
|
<goals>
|
||||||
|
<goal>sign</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-artifact-plugin</artifactId>
|
||||||
|
<version>3.7.0</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>com.diffplug.spotless</groupId>
|
||||||
|
<artifactId>spotless-maven-plugin</artifactId>
|
||||||
|
<version>3.1.0</version>
|
||||||
|
<configuration>
|
||||||
|
<formats>
|
||||||
|
<format>
|
||||||
|
<includes>
|
||||||
|
<include>.gitignore</include>
|
||||||
|
</includes>
|
||||||
|
<trimTrailingWhitespace/>
|
||||||
|
<endWithNewline/>
|
||||||
|
<indent>
|
||||||
|
<tabs>false</tabs>
|
||||||
|
<spaces>true</spaces>
|
||||||
|
<spacesPerTab>4</spacesPerTab>
|
||||||
|
</indent>
|
||||||
|
</format>
|
||||||
|
</formats>
|
||||||
|
<java>
|
||||||
|
<palantirJavaFormat/>
|
||||||
|
</java>
|
||||||
|
<pom>
|
||||||
|
<includes>
|
||||||
|
<include>pom.xml</include>
|
||||||
|
</includes>
|
||||||
|
<sortPom/>
|
||||||
|
</pom>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-deploy-plugin</artifactId>
|
||||||
|
<version>3.2.0</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.gmavenplus</groupId>
|
||||||
|
<artifactId>gmavenplus-plugin</artifactId>
|
||||||
|
<version>4.3.1</version>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.groovy</groupId>
|
||||||
|
<artifactId>groovy</artifactId>
|
||||||
|
<version>5.0.3</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>execute</goal>
|
||||||
|
</goals>
|
||||||
|
<phase>generate-resources</phase>
|
||||||
|
<configuration>
|
||||||
|
<scripts>
|
||||||
|
<script>def
|
||||||
|
file = new File(".github/workflows/Lockfile.yml")
|
||||||
|
project.properties.action_content = file.getText()</script>
|
||||||
|
</scripts>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-resources-plugin</artifactId>
|
||||||
|
<version>3.5.0</version>
|
||||||
|
<!-- kept plugin-level: the release workflows call `mvn resources:copy-resources` directly
|
||||||
|
from the CLI to regenerate action.yml/.mvn/extensions.xml at a specific point in the
|
||||||
|
release job, not as part of every normal build. -->
|
||||||
|
<configuration>
|
||||||
|
<outputDirectory>${project.basedir}</outputDirectory>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>./template/</directory>
|
||||||
|
<includes>
|
||||||
|
<include>action.yml</include>
|
||||||
|
</includes>
|
||||||
|
<filtering>true</filtering>
|
||||||
|
</resource>
|
||||||
|
<resource>
|
||||||
|
<directory>./template/</directory>
|
||||||
|
<includes>
|
||||||
|
<include>extensions.xml</include>
|
||||||
|
</includes>
|
||||||
|
<targetPath>.mvn</targetPath>
|
||||||
|
<filtering>true</filtering>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
<encoding>UTF-8</encoding>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<!-- Disabled: otherwise these run on every normal build too, not just the release
|
||||||
|
workflow's explicit `resources:copy-resources` call. -->
|
||||||
|
<execution>
|
||||||
|
<id>default-resources</id>
|
||||||
|
<phase>none</phase>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>default-testResources</id>
|
||||||
|
<phase>none</phase>
|
||||||
|
</execution>
|
||||||
|
<!-- copy-resources (unlike the resources goal) accepts an outputDirectory/resources
|
||||||
|
override, so src/main/resources still reaches target/classes -->
|
||||||
|
<execution>
|
||||||
|
<id>copy-main-resources</id>
|
||||||
|
<goals>
|
||||||
|
<goal>copy-resources</goal>
|
||||||
|
</goals>
|
||||||
|
<phase>process-resources</phase>
|
||||||
|
<configuration>
|
||||||
|
<outputDirectory>${project.build.outputDirectory}</outputDirectory>
|
||||||
|
<resources combine.self="override">
|
||||||
|
<resource>
|
||||||
|
<directory>${project.basedir}/src/main/resources</directory>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.16.0</version>
|
||||||
|
<configuration/>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-enforcer-plugin</artifactId>
|
||||||
|
<version>3.6.3</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>enforce-plugin-versions</id>
|
||||||
|
<goals>
|
||||||
|
<goal>enforce</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<rules>
|
||||||
|
<requirePluginVersions>
|
||||||
|
<message>Best Practice is to always define plugin versions!</message>
|
||||||
|
<banLatest>true</banLatest>
|
||||||
|
<banRelease>true</banRelease>
|
||||||
|
<banSnapshots>true</banSnapshots>
|
||||||
|
<phases>clean,deploy,site</phases>
|
||||||
|
<additionalPlugins>
|
||||||
|
<additionalPlugin>org.apache.maven.plugins:maven-reactor-plugin</additionalPlugin>
|
||||||
|
</additionalPlugins>
|
||||||
|
<unCheckedPluginList>org.apache.maven.plugins:maven-enforcer-plugin,org.apache.maven.plugins:maven-idea-plugin,org.apache.maven.plugins:maven-reactor-plugin</unCheckedPluginList>
|
||||||
|
</requirePluginVersions>
|
||||||
|
</rules>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-site-plugin</artifactId>
|
||||||
|
<version>3.22.0</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-clean-plugin</artifactId>
|
||||||
|
<version>3.5.0</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-install-plugin</artifactId>
|
||||||
|
<version>3.2.0</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>3.6.0</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<version>3.5.1</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>test-jar</id>
|
||||||
|
<goals>
|
||||||
|
<goal>test-jar</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-plugin-plugin</artifactId>
|
||||||
|
<version>3.16.0</version>
|
||||||
|
<configuration>
|
||||||
|
<!-- see https://issues.apache.org/jira/browse/MNG-5346 -->
|
||||||
|
<goalPrefix>configurator</goalPrefix>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>mojo-descriptor</id>
|
||||||
|
<goals>
|
||||||
|
<goal>descriptor</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>com.soebes.itf.jupiter.extension</groupId>
|
||||||
|
<artifactId>itf-maven-plugin</artifactId>
|
||||||
|
<version>0.13.1</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>installing</id>
|
||||||
|
<goals>
|
||||||
|
<goal>install</goal>
|
||||||
|
<goal>resources-its</goal>
|
||||||
|
</goals>
|
||||||
|
<phase>pre-integration-test</phase>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-failsafe-plugin</artifactId>
|
||||||
|
<version>3.6.0</version>
|
||||||
|
<configuration>
|
||||||
|
<!--
|
||||||
|
! currently needed to run integration tests.
|
||||||
|
-->
|
||||||
|
<systemPropertyVariables>
|
||||||
|
<maven.version>${maven.version}</maven.version>
|
||||||
|
<maven.home>${maven.home}</maven.home>
|
||||||
|
</systemPropertyVariables>
|
||||||
|
<properties>
|
||||||
|
<configurationParameters>junit.jupiter.execution.parallel.enabled=true
|
||||||
|
junit.jupiter.execution.parallel.mode.default=concurrent
|
||||||
|
junit.jupiter.execution.parallel.mode.classes.default=same_thread
|
||||||
|
junit.jupiter.execution.parallel.config.strategy=fixed
|
||||||
|
junit.jupiter.execution.parallel.config.fixed.parallelism=6</configurationParameters>
|
||||||
|
</properties>
|
||||||
|
<rerunFailingTestsCount>2</rerunFailingTestsCount>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>integration-test</goal>
|
||||||
|
<goal>verify</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
<profiles>
|
||||||
|
<profile>
|
||||||
|
<id>publication</id>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
|
<version>3.12.0</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>attach-javadocs</id>
|
||||||
|
<goals>
|
||||||
|
<goal>jar</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<attach>true</attach>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-source-plugin</artifactId>
|
||||||
|
<version>3.4.0</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>attach-sources</id>
|
||||||
|
<goals>
|
||||||
|
<goal>jar</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<attach>true</attach>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-deploy-plugin</artifactId>
|
||||||
|
<version>3.2.0</version>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</profile>
|
||||||
|
</profiles>
|
||||||
|
</project>
|
||||||
@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"artifactId": "module-a",
|
||||||
|
"groupId": "com.mycompany.app",
|
||||||
|
"version": "1.0.0-SNAPSHOT",
|
||||||
|
"pom": {
|
||||||
|
"groupId": "com.mycompany.app",
|
||||||
|
"artifactId": "module-a",
|
||||||
|
"version": "1.0.0-SNAPSHOT",
|
||||||
|
"relativePath": "pom.xml",
|
||||||
|
"checksumAlgorithm": "SHA-256",
|
||||||
|
"checksum": "",
|
||||||
|
"parent": {
|
||||||
|
"groupId": "com.mycompany.app",
|
||||||
|
"artifactId": "reactor-snapshot-sibling-parent",
|
||||||
|
"version": "1.0.0-SNAPSHOT",
|
||||||
|
"relativePath": "../pom.xml",
|
||||||
|
"checksumAlgorithm": "SHA-256",
|
||||||
|
"checksum": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lockFileVersion": 1,
|
||||||
|
"dependencies": [
|
||||||
|
{
|
||||||
|
"groupId": "com.mycompany.app",
|
||||||
|
"artifactId": "module-b",
|
||||||
|
"version": "1.0.0-SNAPSHOT",
|
||||||
|
"checksumAlgorithm": "SHA-256",
|
||||||
|
"checksum": "",
|
||||||
|
"scope": "compile",
|
||||||
|
"resolved": "",
|
||||||
|
"repositoryId": "",
|
||||||
|
"parentPom": {
|
||||||
|
"groupId": "com.mycompany.app",
|
||||||
|
"artifactId": "reactor-snapshot-sibling-parent",
|
||||||
|
"version": "1.0.0-SNAPSHOT",
|
||||||
|
"relativePath": "pom.xml",
|
||||||
|
"checksumAlgorithm": "SHA-256",
|
||||||
|
"checksum": ""
|
||||||
|
},
|
||||||
|
"selectedVersion": "1.0.0-SNAPSHOT",
|
||||||
|
"included": true,
|
||||||
|
"id": "com.mycompany.app:module-b:1.0.0-SNAPSHOT",
|
||||||
|
"children": [],
|
||||||
|
"boms": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"mavenPlugins": [],
|
||||||
|
"mavenExtensions": [],
|
||||||
|
"metaData": {
|
||||||
|
"config": {
|
||||||
|
"includeMavenPlugins": false,
|
||||||
|
"allowValidationFailure": false,
|
||||||
|
"allowPomValidationFailure": false,
|
||||||
|
"allowMavenPluginValidationFailure": false,
|
||||||
|
"allowEnvironmentalValidationFailure": false,
|
||||||
|
"includeEnvironment": false,
|
||||||
|
"reduced": false,
|
||||||
|
"mavenLockfileVersion": "5.17.1-SNAPSHOT",
|
||||||
|
"checksumMode": "local",
|
||||||
|
"checksumAlgorithm": "SHA-256",
|
||||||
|
"includeBoms": true,
|
||||||
|
"allowBomValidationFailure": false,
|
||||||
|
"includeParentPom": true,
|
||||||
|
"allowParentPomValidationFailure": false,
|
||||||
|
"includeMavenExtensions": true,
|
||||||
|
"allowMavenExtensionsValidationFailure": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"boms": []
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
<project>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.mycompany.app</groupId>
|
||||||
|
<artifactId>reactor-snapshot-sibling-parent</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<artifactId>module-a</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.mycompany.app</groupId>
|
||||||
|
<artifactId>module-b</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>io.github.chains-project</groupId>
|
||||||
|
<artifactId>maven-lockfile</artifactId>
|
||||||
|
<version>@project.version@</version>
|
||||||
|
<executions>
|
||||||
|
<execution><goals><goal>validate</goal></goals></execution>
|
||||||
|
</executions>
|
||||||
|
<configuration>
|
||||||
|
<checksumMode>local</checksumMode>
|
||||||
|
<includeMavenPlugins>false</includeMavenPlugins>
|
||||||
|
<includeEnvironment>false</includeEnvironment>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
<project>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.mycompany.app</groupId>
|
||||||
|
<artifactId>reactor-snapshot-sibling-parent</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>module-b</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
</project>
|
||||||
16
tools/proof-of-software/test/fixturi-maven/reactor/pom.xml
Normal file
16
tools/proof-of-software/test/fixturi-maven/reactor/pom.xml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<project>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>com.mycompany.app</groupId>
|
||||||
|
<artifactId>reactor-snapshot-sibling-parent</artifactId>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.target>11</maven.compiler.target>
|
||||||
|
<maven.compiler.source>11</maven.compiler.source>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
<modules>
|
||||||
|
<module>module-b</module>
|
||||||
|
<module>module-a</module>
|
||||||
|
</modules>
|
||||||
|
</project>
|
||||||
24
tools/proof-of-software/test/sbom-maven-control-negativ.mjs
Normal file
24
tools/proof-of-software/test/sbom-maven-control-negativ.mjs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// Controlul negativ al SBOM-ului Maven (sbom-maven.test.mjs, Proof of Software 1.6.0), prin mecanismul comun din _control.mjs:
|
||||||
|
// fiecare paznic scos intr-o copie a lui pos.mjs trebuie sa inroseasca EXACT proba lui, cu suita chiar rulata (trei stari: ROSU cum
|
||||||
|
// trebuia / CONTROL CAZUT / STRICAT cand ancora nu se poate pune sau suita nu ajunge la rezumat).
|
||||||
|
// node aerenew/tools/proof-of-software/test/sbom-maven-control-negativ.mjs
|
||||||
|
import { controleaza } from './_control.mjs';
|
||||||
|
|
||||||
|
process.exitCode = await controleaza('sbom-maven.test.mjs', [
|
||||||
|
['nodurile scoase de mediere listate drept componente', 'const mediat = n.included !== true && n.selectedVersion != null;', 'const mediat = false;', 'componentele = artefactele INCLUSE'],
|
||||||
|
['artefactele inregistrate dinamic nu mai sunt marcate', 'dinamic: n.selectedVersion == null }, id);', 'dinamic: false }, id);', 'componentele = artefactele INCLUSE'],
|
||||||
|
['muchia unui copil scos de mediere nu mai trimite la versiunea aleasa', "muchii.push([deLa, purlMavenQ(n.groupId, n.artifactId, n.selectedVersion, classifier, type), `${id} (mediated to ${n.selectedVersion})`]);", '', 'graful: radacina'],
|
||||||
|
['hash-urile nu mai sunt scrise', '...(c.hash && MAVEN_HEX[c.hash.alg] ? { hashes: [{ alg: c.hash.alg, content: c.hash.sum }] } : {}),', '...({}),', 'hashes: fiecare componenta'],
|
||||||
|
['pom.xml comis nu mai e comparat cu pom.checksum (mereu true)', "potrivire = alg ? String(crypto.createHash(alg).update(pomOcteti).digest('hex') === String(j.pom.checksum).toLowerCase()) : `not comparable (${j.pom.checksumAlgorithm})`;", "potrivire = 'true';", 'CONTROL POZITIV al metodei: pom.checksum'],
|
||||||
|
['clasificatorul scos din purl (sources si javadoc ale aceluiasi artefact se suprapun)', "const q = [['classifier', classifier || null], ['type', type && type !== 'jar' ? type : null]]", "const q = [['type', type && type !== 'jar' ? type : null]]", 'purl-uri verificate de mana'],
|
||||||
|
['scope-ul scos din proprietati', "'aere:maven-role': [...c.roluri].sort().join(','), 'aere:maven-scope': c.scopes.size ? [...c.scopes].sort().join(',') : null,", "'aere:maven-role': [...c.roluri].sort().join(','), 'aere:maven-scope': null,", 'scope-urile si rolurile se aduna'],
|
||||||
|
['pluginurile nu mai sunt componente', "const pluginuri = unelte(j.mavenPlugins, 'plugin', 'mavenPlugins'), extensii", 'const pluginuri = 0, extensii', 'pluginurile (17) si pom-urile'],
|
||||||
|
['doua checksum-uri pentru aceeasi coordonata primite', 'if (comp.has(purl) && (c.hash && c.hash.sum) !== (h && h.sum)) throw', 'if (false) throw', 'CONTROL: un lockfile stricat'],
|
||||||
|
['id-ul care nu numeste g:a:v primit', 'if (n.id != null && n.id !== id) throw', 'if (false) throw', 'CONTROL: un lockfile stricat'],
|
||||||
|
['un nod scos de mediere cu copii primit', 'if ((n.children || []).length) throw new Error(`lockfile.json: ${id} is omitted by mediation and still has children`);', '', 'CONTROL: un lockfile stricat'],
|
||||||
|
['tinta unei muchii mediate neverificata', 'for (const [, la, id] of muchii) if (!comp.has(la)) throw', 'for (const [, la, id] of muchii) if (false) throw', 'CONTROL: un lockfile stricat'],
|
||||||
|
['agregatorul fara lockfile nu mai numeste modulele', 'if (module.length) return { lipsa: `the committed tree has no lockfile.json; its pom.xml is an aggregator', 'if (false) return { lipsa: `the committed tree has no lockfile.json; its pom.xml is an aggregator', 'modulele: pom-ul agregator'],
|
||||||
|
['SBOM-ul Maven judecat ca npm la reconstructie', "'lockfile.json': 'maven' };", '};', 'verify --rebuild-from: SBOM-ul Maven'],
|
||||||
|
['numarul de serie aleator (SBOM-ul nu mai e determinist)', 'serialNumber: serieDerivata({ metadata, dependencies, components }),', "serialNumber: 'urn:uuid:' + crypto.randomUUID(),", 'determinist'],
|
||||||
|
['calea radacinii (.) trimisa la git ca `<commit>:.`, pe care git il refuza', "const caleGit = (commit, rel, f = '') => `${commit}:${!rel || rel === '.' ? '' : rel + (f ? '/' : '')}${f}`;", "const caleGit = (commit, rel, f = '') => `${commit}:${!rel ? '' : rel + (f ? '/' : '')}${f}`;", '--source-path . :'],
|
||||||
|
]);
|
||||||
279
tools/proof-of-software/test/sbom-maven.test.mjs
Normal file
279
tools/proof-of-software/test/sbom-maven.test.mjs
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
// Proof of Software 1.6.0: SBOM-ul unui proiect Maven din lockfile.json COMIS (pluginul maven-lockfile, io.github.chains-project) si
|
||||||
|
// pom.xml de langa el, derivat determinist si judecat la verify --rebuild-from. Fixturile sunt fisiere REALE scrise de plugin, extrase cu
|
||||||
|
// `git show` din depozitul public chains-project/maven-lockfile la 083b879a (test/fixturi-maven/README.md: care, de unde, amprentele).
|
||||||
|
// Numaratorile de referinta se fac AICI, cu alt cod decat cel masurat (o parcurgere proprie a lockfile-ului). Fiecare afirmatie cu
|
||||||
|
// perechea ei negativa. Offline. Nimic din afara probelor nu are voie sa arunce: un paznic scos de controlul negativ trebuie sa
|
||||||
|
// inroseasca o proba, nu sa opreasca suita inainte de rezumat.
|
||||||
|
// node test/sbom-maven.test.mjs iesire 0 = toate cum trebuia
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import os from 'node:os';
|
||||||
|
import path from 'node:path';
|
||||||
|
import crypto from 'node:crypto';
|
||||||
|
import { execFileSync } from 'node:child_process';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { attest, verify, sbomMaven, sbomMavenFromTree, sbomSemantica, citestePom, purlMavenQ } from '../pos.mjs';
|
||||||
|
|
||||||
|
const AICI = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
const POS = path.resolve(AICI, '..', 'pos.mjs');
|
||||||
|
const FM = path.join(AICI, 'fixturi-maven');
|
||||||
|
let treceri = 0; const esecuri = [];
|
||||||
|
async function test(nume, fn) { try { await fn(); treceri++; console.log(' OK ' + nume); } catch (e) { esecuri.push(nume); console.log(' ESEC ' + nume + ' — ' + (e.message || e)); } }
|
||||||
|
const cere = (c, m) => { if (!c) throw new Error(m); };
|
||||||
|
const g = (args, cwd) => execFileSync('git', args, { cwd, stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
|
||||||
|
const check = (r, re) => r.checks.find((c) => re.test(c.name));
|
||||||
|
const prop = (c, n) => ((c && c.properties || []).find((x) => x.name === n) || {}).value;
|
||||||
|
const arunca = (f) => { try { f(); return null; } catch (e) { return e.message; } };
|
||||||
|
const incearca = (f) => { try { return f(); } catch (e) { return { eroare: String(e.message || e) }; } };
|
||||||
|
// pentru functiile asincrone (attest): o respingere prinsa aici, nu la nivelul modulului, unde ar opri suita inainte de rezumat (STRICAT, nu ROSU)
|
||||||
|
const incearcaA = async (f) => { try { return await f(); } catch (e) { return { eroare: String(e.message || e) }; } };
|
||||||
|
const bun = (b) => { cere(b && !b.eroare, b && b.eroare); return b; };
|
||||||
|
|
||||||
|
const citeste = (d) => ({ lock: fs.readFileSync(path.join(FM, d, 'lockfile.json'), 'utf8'), pom: fs.readFileSync(path.join(FM, d, 'pom.xml')) });
|
||||||
|
const ML = citeste('maven-lockfile'), MA = citeste('reactor/module-a'), CL = citeste('classifier');
|
||||||
|
const bML = incearca(() => sbomMaven(ML)), bMA = incearca(() => sbomMaven(MA)), bCL = incearca(() => sbomMaven(CL));
|
||||||
|
const J = JSON.parse(ML.lock);
|
||||||
|
const rol = (b, x) => (b.components || []).filter((c) => (prop(c, 'aere:maven-role') || '').split(',').includes(x));
|
||||||
|
const dep = (b, ref) => ((b.dependencies || []).find((d) => d.ref === ref) || { dependsOn: [] }).dependsOn;
|
||||||
|
|
||||||
|
// parcurgerea INDEPENDENTA a lockfile-ului (alt cod decat pos.mjs): fiecare nod cu rolul si parintele lui
|
||||||
|
function umblaLock(j) {
|
||||||
|
const out = [];
|
||||||
|
const umbla = (n, r, parinte) => { out.push({ n, r, parinte }); for (const c of n.children || []) umbla(c, r, n); };
|
||||||
|
for (const d of j.dependencies || []) umbla(d, 'dependency', null);
|
||||||
|
for (const p of j.mavenPlugins || []) for (const d of p.dependencies || []) umbla(d, 'plugin-dependency', p);
|
||||||
|
for (const p of j.mavenExtensions || []) for (const d of p.dependencies || []) umbla(d, 'extension-dependency', p);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
// purl scris de mana (fixturile nu au caractere de codificat); tipul jar nu se scrie, calificatorii in ordinea cheilor
|
||||||
|
const cheia = (n, v = n.version) => { const q = [n.classifier ? 'classifier=' + n.classifier : null, n.type && n.type !== 'jar' ? 'type=' + n.type : null].filter(Boolean); return `pkg:maven/${n.groupId}/${n.artifactId}@${v}` + (q.length ? '?' + q.join('&') : ''); };
|
||||||
|
const cheiaPom = (p) => `pkg:maven/${p.groupId}/${p.artifactId}@${p.version}?type=pom`;
|
||||||
|
// componenta = nod inclus, sau nod fara selectedVersion (inregistrat dinamic, fara mediere); altfel e scos de mediere
|
||||||
|
const esteComponenta = (n) => n.included === true || n.selectedVersion == null;
|
||||||
|
const N = umblaLock(J);
|
||||||
|
const artefacte = new Set(N.filter(({ n }) => esteComponenta(n)).map(({ n }) => cheia(n)));
|
||||||
|
const mediate = N.filter(({ n }) => !esteComponenta(n));
|
||||||
|
const pomuriIndep = new Set();
|
||||||
|
const pomIndep = (p) => { for (let x = p; x; x = x.parent) { pomuriIndep.add(cheiaPom(x)); for (const b of x.boms || []) pomIndep(b); } };
|
||||||
|
for (const { n } of N) { pomIndep(n.parentPom); for (const b of n.boms || []) pomIndep(b); }
|
||||||
|
for (const p of J.mavenPlugins || []) pomIndep(p.parentPom);
|
||||||
|
for (const b of J.boms || []) pomIndep(b);
|
||||||
|
pomIndep(J.pom && J.pom.parent);
|
||||||
|
|
||||||
|
await test('maven-lockfile: radacina din lockfile (groupId:artifactId:version, purl pkg:maven), derived-from lockfile.json; --version o suprascrie si schimba numarul de serie, versiunea proiectului ramane scrisa', () => {
|
||||||
|
bun(bML);
|
||||||
|
cere(bML.metadata.component.purl === 'pkg:maven/io.github.chains-project/maven-lockfile@5.18.7-SNAPSHOT' && bML.metadata.component.group === 'io.github.chains-project' && bML.metadata.component.name === 'maven-lockfile', bML.metadata.component.purl);
|
||||||
|
cere(prop(bML.metadata, 'aere:derived-from') === 'lockfile.json' && prop(bML.metadata, 'aere:maven-lockfile-plugin') === '5.18.6' && prop(bML.metadata, 'aere:maven-checksum-mode') === 'remote' && prop(bML.metadata, 'aere:maven-packaging') === 'maven-plugin', JSON.stringify(bML.metadata.properties));
|
||||||
|
const alt = bun(incearca(() => sbomMaven({ ...ML, version: '9.9.9' })));
|
||||||
|
cere(alt.metadata.component.purl === 'pkg:maven/io.github.chains-project/maven-lockfile@9.9.9' && alt.serialNumber !== bML.serialNumber && prop(alt.metadata, 'aere:maven-project-version') === '5.18.7-SNAPSHOT', alt.metadata.component.purl);
|
||||||
|
});
|
||||||
|
await test('componentele = artefactele INCLUSE din toti arborii (proiectul si cele 17 pluginuri) + cele inregistrate dinamic (fara selectedVersion), o data pe purl; numaratoare independenta; niciun nod scos de mediere nu e componenta; omise numarate', () => {
|
||||||
|
bun(bML);
|
||||||
|
const dinArbori = new Set([...rol(bML, 'dependency'), ...rol(bML, 'plugin-dependency'), ...rol(bML, 'extension-dependency')].map((c) => c.purl));
|
||||||
|
cere(artefacte.size === 325 && dinArbori.size === artefacte.size && [...artefacte].every((k) => dinArbori.has(k)), `independent ${artefacte.size}, in SBOM ${dinArbori.size}`);
|
||||||
|
const scoaseCaComponente = mediate.filter(({ n }) => !artefacte.has(cheia(n)) && bML.components.some((c) => c.purl === cheia(n)));
|
||||||
|
cere(!scoaseCaComponente.length, 'noduri scoase de mediere ajunse componente: ' + scoaseCaComponente.slice(0, 3).map(({ n }) => n.id).join(', '));
|
||||||
|
const conflict = mediate.filter(({ n }) => n.selectedVersion !== n.version).length;
|
||||||
|
cere(mediate.length === 528 && prop(bML.metadata, 'aere:maven-omitted-for-conflict') === String(conflict) && prop(bML.metadata, 'aere:maven-omitted-for-duplicate') === String(mediate.length - conflict), `mediate ${mediate.length}: conflict ${conflict}, SBOM ${prop(bML.metadata, 'aere:maven-omitted-for-conflict')}/${prop(bML.metadata, 'aere:maven-omitted-for-duplicate')}`);
|
||||||
|
const dinamice = new Set(N.filter(({ n }) => n.selectedVersion == null).map(({ n }) => cheia(n)));
|
||||||
|
const marcate = bML.components.filter((c) => prop(c, 'aere:maven-recorded') === 'dynamic').map((c) => c.purl);
|
||||||
|
cere(dinamice.size === 7 && marcate.length === 7 && marcate.every((p) => dinamice.has(p)) && prop(bML.metadata, 'aere:maven-dynamic') === '7', `dinamice independent ${dinamice.size}, marcate ${marcate.length}`);
|
||||||
|
cere(dinamice.has('pkg:maven/org.apache.maven.surefire/surefire-junit-platform@3.6.0'), 'furnizorul surefire adus dinamic lipseste din lista independenta');
|
||||||
|
});
|
||||||
|
await test('purl-uri verificate de mana: gson simplu, guice cu clasificator, un BOM cu type=pom, clasificator+tip in ordinea cheilor (exemplele din purl-spec), tipul jar nu se scrie; sources si javadoc ale aceluiasi artefact sunt doua componente', () => {
|
||||||
|
bun(bML); bun(bCL);
|
||||||
|
const gson = bML.components.find((c) => c.purl === 'pkg:maven/com.google.code.gson/gson@2.14.0');
|
||||||
|
cere(gson && gson.group === 'com.google.code.gson' && gson.name === 'gson' && gson.version === '2.14.0' && gson['bom-ref'] === gson.purl, JSON.stringify(gson));
|
||||||
|
cere(bML.components.some((c) => c.purl === 'pkg:maven/com.google.inject/guice@5.1.0?classifier=classes'), 'guice:classes');
|
||||||
|
const bom = bML.components.find((c) => c.purl === 'pkg:maven/org.junit/junit-bom@5.11.0-M2?type=pom');
|
||||||
|
cere(bom && prop(bom, 'aere:maven-role') === 'bom' && bom.hashes && bom.hashes[0].content === '4a3ffc4a4edcfec2cb5d619922706a9407161798571939f854dfda73e4e17cc8', JSON.stringify(bom));
|
||||||
|
cere(purlMavenQ('g', 'a', '1', 'dist', 'zip') === 'pkg:maven/g/a@1?classifier=dist&type=zip' && purlMavenQ('g', 'a', '1', null, 'jar') === 'pkg:maven/g/a@1' && purlMavenQ('net.sf.jacob-projec', 'jacob', '1.14.3', 'x86', 'dll') === 'pkg:maven/net.sf.jacob-projec/jacob@1.14.3?classifier=x86&type=dll', 'purlMavenQ');
|
||||||
|
// fixtura are artefactul simplu SI cele doua clasificate: trei componente, trei checksum-uri (fara clasificator in purl s-ar suprapune)
|
||||||
|
const cl = bCL.components.filter((c) => c.name === 'junit-jupiter-api').map((c) => c.purl).sort();
|
||||||
|
cere(cl.join(' ') === 'pkg:maven/org.junit.jupiter/junit-jupiter-api@5.9.2 pkg:maven/org.junit.jupiter/junit-jupiter-api@5.9.2?classifier=javadoc pkg:maven/org.junit.jupiter/junit-jupiter-api@5.9.2?classifier=sources', cl.join(' '));
|
||||||
|
cere(new Set(bCL.components.filter((c) => c.name === 'junit-jupiter-api').map((c) => c.hashes[0].content)).size === 3, 'cele trei nu au trei checksum-uri distincte');
|
||||||
|
});
|
||||||
|
await test('hashes: fiecare componenta cu checksum poarta SHA-256 egal cu checksum-ul din lockfile (617/617, numaratoare independenta); fara checksum (modul din acelasi reactor) -> fara hashes, marcat si numarat', () => {
|
||||||
|
bun(bML); bun(bMA);
|
||||||
|
const indep = new Map();
|
||||||
|
for (const { n } of N) if (esteComponenta(n)) indep.set(cheia(n), n.checksum);
|
||||||
|
const pom = (p) => { for (let x = p; x; x = x.parent) { indep.set(cheiaPom(x), x.checksum); for (const b of x.boms || []) pom(b); } };
|
||||||
|
for (const { n } of N) { pom(n.parentPom); for (const b of n.boms || []) pom(b); }
|
||||||
|
for (const p of J.mavenPlugins || []) { indep.set(cheia(p), p.checksum); pom(p.parentPom); }
|
||||||
|
cere(indep.size === 617 && bML.components.length === 617, `independent ${indep.size}, SBOM ${bML.components.length}`);
|
||||||
|
let n = 0;
|
||||||
|
for (const c of bML.components) { cere(c.hashes && c.hashes.length === 1 && c.hashes[0].alg === 'SHA-256' && c.hashes[0].content === indep.get(c.purl), `${c.purl}: ${JSON.stringify(c.hashes)} fata de ${indep.get(c.purl)}`); n++; }
|
||||||
|
cere(n === 617 && prop(bML.metadata, 'aere:maven-without-checksum') === '0', 'comparate ' + n);
|
||||||
|
const mb = bMA.components.find((c) => c.name === 'module-b');
|
||||||
|
cere(mb && !mb.hashes && prop(mb, 'aere:maven-no-checksum') === 'true' && prop(mb, 'aere:maven-scope') === 'compile' && prop(bMA.metadata, 'aere:maven-without-checksum') === '2', JSON.stringify(bMA.components));
|
||||||
|
});
|
||||||
|
await test('CONTROL POZITIV al metodei: pom.checksum din lockfile e chiar SHA-256 al pom.xml COMIS (calculat aici, nu in pos.mjs), deci aere:maven-pom-matches-lockfile masoara; un octet in plus in pom -> false; checksum gol -> unrecorded', () => {
|
||||||
|
bun(bML); bun(bMA); bun(bCL);
|
||||||
|
cere(crypto.createHash('sha256').update(ML.pom).digest('hex') === J.pom.checksum && J.pom.checksumAlgorithm === 'SHA-256', 'pom.checksum nu e SHA-256 al pom.xml comis');
|
||||||
|
cere(prop(bML.metadata, 'aere:maven-pom-matches-lockfile') === 'true' && prop(bCL.metadata, 'aere:maven-pom-matches-lockfile') === 'true', 'potrivirea');
|
||||||
|
const alt = bun(incearca(() => sbomMaven({ lock: ML.lock, pom: Buffer.concat([ML.pom, Buffer.from('\n')]) })));
|
||||||
|
cere(prop(alt.metadata, 'aere:maven-pom-matches-lockfile') === 'false' && alt.serialNumber !== bML.serialNumber, 'CONTROL: un pom schimbat trebuia sa dea false');
|
||||||
|
cere(prop(bMA.metadata, 'aere:maven-pom-matches-lockfile') === 'unrecorded', prop(bMA.metadata, 'aere:maven-pom-matches-lockfile'));
|
||||||
|
});
|
||||||
|
await test('CONTROL POZITIV al metodei: SHA-256 din SBOM e chiar hash-ul jar-ului din memoria locala Maven (~/.m2; sarit daca lipseste)', () => {
|
||||||
|
bun(bML);
|
||||||
|
const M2 = path.join(os.homedir(), '.m2', 'repository'); let n = 0;
|
||||||
|
for (const c of rol(bML, 'dependency')) {
|
||||||
|
if (/[?]/.test(c.purl)) continue;
|
||||||
|
const f = path.join(M2, ...c.group.split('.'), c.name, c.version, `${c.name}-${c.version}.jar`); if (!fs.existsSync(f)) continue;
|
||||||
|
cere(crypto.createHash('sha256').update(fs.readFileSync(f)).digest('hex') === c.hashes[0].content, `${c.purl}: hash-ul fisierului difera`); n++;
|
||||||
|
}
|
||||||
|
if (!n) { console.log(' SARIT (niciun jar al acestor coordonate in memoria locala Maven a acestei masini; masuratoarea pe registru e in fixturi-maven/README.md)'); return; }
|
||||||
|
console.log(` (${n} jar-uri comparate cu memoria locala Maven)`);
|
||||||
|
});
|
||||||
|
await test('graful: radacina -> nodurile de sus; parinte -> copil; un copil scos de mediere trimite la versiunea ALEASA (nu la cea scoasa); plugin -> dependintele lui; fara referinte orfane', () => {
|
||||||
|
bun(bML);
|
||||||
|
const root = bML.metadata.component['bom-ref'];
|
||||||
|
const tinta = (n) => (esteComponenta(n) ? cheia(n) : cheia(n, n.selectedVersion));
|
||||||
|
cere(dep(bML, root).join(' ') === [...new Set(J.dependencies.map(tinta))].sort().join(' '), 'radacina: ' + dep(bML, root).length);
|
||||||
|
for (const { n, parinte } of mediate) { const p = parinte ? cheia(parinte) : root; cere(dep(bML, p).includes(cheia(n, n.selectedVersion)), `${n.id} scos de mediere: parintele ${p} nu depinde de versiunea aleasa ${n.selectedVersion}`); }
|
||||||
|
const ex = mediate.find(({ n }) => n.selectedVersion !== n.version && !artefacte.has(cheia(n)));
|
||||||
|
cere(ex && !dep(bML, cheia(ex.parinte)).includes(cheia(ex.n)), ex ? `${ex.n.id}: muchia catre versiunea scoasa nu are voie sa existe` : 'niciun conflict cu versiunea scoasa absenta dintre componente');
|
||||||
|
const pl = J.mavenPlugins[0];
|
||||||
|
cere(dep(bML, cheia(pl)).join(' ') === [...new Set(pl.dependencies.map(tinta))].sort().join(' ') && pl.artifactId === 'spotless-maven-plugin', 'pluginul ' + pl.artifactId);
|
||||||
|
const refs = new Set([root, ...bML.components.map((c) => c['bom-ref'])]);
|
||||||
|
const orfane = bML.dependencies.flatMap((d) => [d.ref, ...d.dependsOn]).filter((r) => !refs.has(r));
|
||||||
|
cere(!orfane.length && bML.dependencies.length === 218, 'orfane ' + orfane.length + ', inregistrari ' + bML.dependencies.length);
|
||||||
|
});
|
||||||
|
await test('scope-urile si rolurile se aduna pe componenta: aceeasi coordonata in arborele proiectului si al unui plugin e O componenta cu doua roluri (13) si cu scope-urile amandurora (23 cu doua scope-uri); un plugin are rol plugin, hash, depozit, fara scope', () => {
|
||||||
|
bun(bML);
|
||||||
|
const roluri = new Map(), scopuri = new Map();
|
||||||
|
for (const { n, r } of N) if (esteComponenta(n)) { const k = cheia(n); roluri.set(k, (roluri.get(k) || new Set()).add(r)); scopuri.set(k, (scopuri.get(k) || new Set()).add(n.scope)); }
|
||||||
|
const douaRoluri = [...roluri.entries()].filter(([, s]) => s.size > 1), douaScopuri = [...scopuri.entries()].filter(([, s]) => s.size > 1);
|
||||||
|
cere(douaRoluri.length === 13 && douaRoluri.every(([k]) => prop(bML.components.find((c) => c.purl === k), 'aere:maven-role') === 'dependency,plugin-dependency'), 'doua roluri: ' + douaRoluri.length);
|
||||||
|
cere(douaScopuri.length === 23 && douaScopuri.every(([k, s]) => prop(bML.components.find((c) => c.purl === k), 'aere:maven-scope') === [...s].sort().join(',')), 'doua scope-uri: ' + douaScopuri.length);
|
||||||
|
cere([...scopuri.entries()].every(([k, s]) => prop(bML.components.find((c) => c.purl === k), 'aere:maven-scope') === [...s].sort().join(',')), 'scope-ul unei componente difera de reuniunea independenta');
|
||||||
|
const pl = bML.components.find((c) => c.name === 'spotless-maven-plugin');
|
||||||
|
cere(pl && prop(pl, 'aere:maven-role') === 'plugin' && prop(pl, 'aere:maven-scope') == null && pl.hashes && prop(pl, 'aere:maven-repository') === 'central', JSON.stringify(pl));
|
||||||
|
});
|
||||||
|
await test('pluginurile (17) si pom-urile (parinte si BOM, type=pom, cu hash; numaratoare independenta) sunt componente cu rolul lor; radacina nu depinde de pluginuri; pom-urile nu au muchii', () => {
|
||||||
|
bun(bML);
|
||||||
|
cere(rol(bML, 'plugin').length === 17 && J.mavenPlugins.length === 17 && prop(bML.metadata, 'aere:maven-plugins') === '17', 'pluginuri ' + rol(bML, 'plugin').length);
|
||||||
|
const pomuri = new Set([...rol(bML, 'parent-pom'), ...rol(bML, 'bom')].map((c) => c.purl));
|
||||||
|
cere(pomuri.size === pomuriIndep.size && [...pomuriIndep].every((k) => pomuri.has(k)) && pomuriIndep.size === 275, `pom-uri independent ${pomuriIndep.size}, SBOM ${pomuri.size}`);
|
||||||
|
const root = bML.metadata.component['bom-ref'], pluginuri = new Set(rol(bML, 'plugin').map((c) => c.purl));
|
||||||
|
cere(!dep(bML, root).some((r) => pluginuri.has(r)), 'radacina depinde de un plugin');
|
||||||
|
const tinte = new Set(bML.dependencies.flatMap((d) => d.dependsOn));
|
||||||
|
cere(![...pomuri].some((p) => tinte.has(p) || bML.dependencies.some((d) => d.ref === p)), 'un pom are muchii');
|
||||||
|
});
|
||||||
|
await test('determinist: aceleasi fisiere dau aceiasi octeti (trei fixturi); numarul de serie e UUID derivat din continut; alta versiune, alt numar de serie', () => {
|
||||||
|
bun(bML); bun(bMA); bun(bCL);
|
||||||
|
cere(JSON.stringify(bun(incearca(() => sbomMaven(ML)))) === JSON.stringify(bML) && JSON.stringify(bun(incearca(() => sbomMaven(MA)))) === JSON.stringify(bMA) && JSON.stringify(bun(incearca(() => sbomMaven(CL)))) === JSON.stringify(bCL), 'doua derivari difera');
|
||||||
|
cere(/^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(bML.serialNumber) && !('timestamp' in bML.metadata), bML.serialNumber);
|
||||||
|
cere(bun(incearca(() => sbomMaven({ ...CL, version: '2' }))).serialNumber !== bCL.serialNumber, 'CONTROL: alta versiune, alt numar de serie');
|
||||||
|
});
|
||||||
|
await test('CONTROL: un lockfile stricat e refuzat cu motivul, nu ghicit (nu e JSON, alta versiune de lockfile, nod fara versiune, id care nu numeste g:a:v, scope necunoscut, checksum care nu e hex, doua checksum-uri, mediat cu copii, inclus cu alta versiune aleasa, mediat catre o versiune pe care n-o are nimeni)', () => {
|
||||||
|
const j = () => JSON.parse(CL.lock); const cu = (x) => arunca(() => sbomMaven({ lock: x, pom: CL.pom })) || 'ACCEPTAT';
|
||||||
|
const a = j(); a.lockFileVersion = 2;
|
||||||
|
const b = j(); delete b.dependencies[0].version;
|
||||||
|
const c = j(); c.dependencies[0].id = 'org.junit.jupiter:junit-jupiter-api:5.9.3';
|
||||||
|
const d = j(); d.dependencies[0].scope = 'floating';
|
||||||
|
const e = j(); e.dependencies[0].checksum = 'zz' + e.dependencies[0].checksum.slice(2);
|
||||||
|
const f = JSON.parse(ML.lock); { const vazute = new Set(); let gata = false; for (const { n } of umblaLock(f)) { if (gata || !esteComponenta(n)) continue; const k = cheia(n); if (vazute.has(k)) { n.checksum = 'a'.repeat(64); gata = true; } vazute.add(k); } cere(gata, 'fixtura: nicio coordonata inclusa de doua ori'); }
|
||||||
|
const h = j(); { const m = umblaLock(h).find(({ n }) => !esteComponenta(n)); m.n.children = [JSON.parse(JSON.stringify(h.dependencies[0]))]; }
|
||||||
|
const i = j(); i.dependencies[0].selectedVersion = '5.9.9';
|
||||||
|
const k = j(); { const m = umblaLock(k).find(({ n }) => !esteComponenta(n)); m.n.selectedVersion = '9.9.9'; }
|
||||||
|
const m = [cu('{'), cu(a), cu(b), cu(c), cu(d), cu(e), cu(f), cu(h), cu(i), cu(k)];
|
||||||
|
cere(/not JSON/.test(m[0]) && /lockFileVersion 2/.test(m[1]) && /no groupId, artifactId or version/.test(m[2]) && /does not name/.test(m[3]) && /unknown scope/.test(m[4]) && /not SHA-256 hex/.test(m[5]) && /two different checksums/.test(m[6]) && /still has children/.test(m[7]) && /names another selected version/.test(m[8]) && /points to .* which no included node has/.test(m[9]), m.join(' | '));
|
||||||
|
const p1 = arunca(() => sbomMaven({ lock: CL.lock, pom: '<project><artifactId>x</artifactId>' })) || 'ACCEPTAT', p2 = arunca(() => sbomMaven({ lock: CL.lock, pom: '<settings/>' })) || 'ACCEPTAT', p3 = arunca(() => sbomMaven({ lock: CL.lock, pom: null })) || 'ACCEPTAT';
|
||||||
|
cere(/pom.xml: <project> is not closed/.test(p1) && /no <project> element/.test(p2) && /needs the committed pom.xml/.test(p3), [p1, p2, p3].join(' | '));
|
||||||
|
});
|
||||||
|
await test('citestePom: coordonatele mostenite din <parent>, packaging, <modules> ale proiectului; modulele din profile nu se iau; refuzuri cu motivul', () => {
|
||||||
|
const r = citestePom(fs.readFileSync(path.join(FM, 'reactor', 'pom.xml'), 'utf8')), a = citestePom(fs.readFileSync(path.join(FM, 'reactor', 'module-a', 'pom.xml'), 'utf8'));
|
||||||
|
cere(r.packaging === 'pom' && r.modules.join(',') === 'module-b,module-a' && r.groupId === 'com.mycompany.app', JSON.stringify(r));
|
||||||
|
cere(a.artifactId === 'module-a' && a.groupId === 'com.mycompany.app' && a.version === '1.0.0-SNAPSHOT' && a.parent.artifactId === 'reactor-snapshot-sibling-parent' && !a.modules.length, JSON.stringify(a));
|
||||||
|
const p = citestePom('<?xml version="1.0"?>\n<project xmlns="http://maven.apache.org/POM/4.0.0"><!-- c --><artifactId>x</artifactId><profiles><profile><modules><module>nu</module></modules></profile></profiles><modules><module>da</module></modules></project>');
|
||||||
|
cere(p.modules.join(',') === 'da' && p.groupId === null, JSON.stringify(p));
|
||||||
|
cere(/names no artifactId/.test(arunca(() => citestePom('<project/>')) || '') && /DOCTYPE/.test(arunca(() => citestePom('<!DOCTYPE x><project/>')) || ''), 'refuzuri');
|
||||||
|
});
|
||||||
|
await test('forma semantica: componentele Maven intra cu purl, hash si proprietatile aere: (rol, scope); radacina prin purl', () => {
|
||||||
|
bun(bML);
|
||||||
|
const s = sbomSemantica(bML);
|
||||||
|
cere(s.radacina === 'pkg:maven/io.github.chains-project/maven-lockfile@5.18.7-SNAPSHOT' && s.componente.every((c) => c.hashes.length === 1 && c.props.some((p) => p.startsWith('aere:maven-role='))) && s.meta.some((p) => p === 'aere:maven-pom-matches-lockfile=true'), JSON.stringify(s.componente[0]));
|
||||||
|
});
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- fluxul intreg, intr-un depozit git temporar
|
||||||
|
const G = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-mv-g-'));
|
||||||
|
g(['init', '-q'], G); g(['config', 'user.email', 'proba@aere.invalid'], G); g(['config', 'user.name', 'proba'], G); g(['config', 'core.autocrlf', 'false'], G);
|
||||||
|
fs.cpSync(path.join(FM, 'maven-lockfile'), path.join(G, 'java'), { recursive: true });
|
||||||
|
fs.cpSync(path.join(FM, 'reactor'), path.join(G, 'reactor'), { recursive: true });
|
||||||
|
fs.cpSync(path.join(FM, 'classifier'), G, { recursive: true }); // lockfile.json si pom.xml la RADACINA depozitului
|
||||||
|
fs.mkdirSync(path.join(G, 'cu-module', 'module-a'), { recursive: true }); // un pom cu <modules> care are si lockfile.json; numai module-a are lockfile
|
||||||
|
fs.copyFileSync(path.join(FM, 'reactor', 'pom.xml'), path.join(G, 'cu-module', 'pom.xml')); fs.copyFileSync(path.join(FM, 'reactor', 'module-a', 'lockfile.json'), path.join(G, 'cu-module', 'lockfile.json'));
|
||||||
|
fs.copyFileSync(path.join(FM, 'reactor', 'module-a', 'lockfile.json'), path.join(G, 'cu-module', 'module-a', 'lockfile.json'));
|
||||||
|
fs.mkdirSync(path.join(G, 'fara-pom')); fs.copyFileSync(path.join(FM, 'classifier', 'lockfile.json'), path.join(G, 'fara-pom', 'lockfile.json'));
|
||||||
|
fs.mkdirSync(path.join(G, 'nimic')); fs.writeFileSync(path.join(G, 'nimic', 'README'), 'fara maven\n');
|
||||||
|
g(['add', '-A'], G); g(['commit', '-q', '-m', 'proiecte Maven de proba'], G);
|
||||||
|
const HEAD = g(['rev-parse', 'HEAD'], G);
|
||||||
|
const C = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-mv-c-')); g(['clone', '-q', G, C], os.tmpdir());
|
||||||
|
const OUT = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-mv-o-'));
|
||||||
|
const BIN = path.join(OUT, 'app.jar'); fs.writeFileSync(BIN, 'binarul construit altfel decat cu npm pack');
|
||||||
|
const S = path.join(OUT, 'java.sbom.cdx.json'), SR = path.join(OUT, 'radacina.sbom.cdx.json');
|
||||||
|
const cli = (args) => incearca(() => execFileSync(process.execPath, [POS, 'sbom-maven', ...args], { cwd: G, stdio: ['ignore', 'pipe', 'pipe'] }).toString());
|
||||||
|
const cliJ = cli(['--source-path', 'java', '--out', S]), cliR = cli(['--source-path', '.', '--out', SR]);
|
||||||
|
const attJ = await incearcaA(() => attest({ artifacts: [BIN], sbom: S, name: 'maven-lockfile', sourcePath: 'java', cwd: G }));
|
||||||
|
const attR = await incearcaA(() => attest({ artifacts: [BIN], sbom: SR, name: 'classifier-dependency-check-correct', version: '1', sourcePath: '.', cwd: G }));
|
||||||
|
|
||||||
|
await test('sbom-maven (linia de comanda) scrie din arborele COMIS exact SBOM-ul pe care il re-deriva verificarea, si spune ce a numarat; --version e optional', () => {
|
||||||
|
cere(typeof cliJ === 'string' && /71 dependency artifact\(s\), 17 plugin\(s\) with 267 plugin dependencies, 275 POM\(s\)\/BOM\(s\); 617 of 617 components with a file hash; 218 dependency record\(s\); 159 node\(s\) omitted for conflict, 369 for duplicate; pom.xml matches the lockfile: true; modules: none/.test(cliJ), JSON.stringify(cliJ));
|
||||||
|
cere(fs.readFileSync(S, 'utf8') === JSON.stringify(sbomMavenFromTree(G, HEAD, 'java', null).bom, null, 1) + '\n', 'fisierul scris difera de derivarea din arbore');
|
||||||
|
});
|
||||||
|
await test('--source-path . : un lockfile.json la RADACINA depozitului (cazul obisnuit Maven) se deriva, se atesta cu source.path "." si se re-deriva din clona', async () => {
|
||||||
|
cere(typeof cliR === 'string' && /6 dependency artifact\(s\), 0 plugin\(s\)/.test(cliR), JSON.stringify(cliR));
|
||||||
|
cere(fs.readFileSync(SR, 'utf8') === JSON.stringify(sbomMavenFromTree(G, HEAD, '.', '1').bom, null, 1) + '\n', 'fisierul scris la radacina difera de derivarea din arbore');
|
||||||
|
cere(!attR.eroare && attR.statement.source.path === '.' && attR.statement.source.tree === g(['rev-parse', 'HEAD:'], G), JSON.stringify(attR.eroare || attR.statement.source));
|
||||||
|
const r = await verify(attR, [BIN, SR], { rebuildFrom: C });
|
||||||
|
const sb = check(r, /attested SBOM says what the committed lockfile.json \(maven-lockfile\) resolves \(7 components\)/), arb = check(r, /is the attested tree/);
|
||||||
|
cere(r.valid && sb && sb.pass === true && arb && arb.pass === true, JSON.stringify(r.checks.filter((c) => c.pass !== true)));
|
||||||
|
});
|
||||||
|
await test('verify --rebuild-from: SBOM-ul Maven spune ce rezolva lockfile.json-ul comis (VALID; artefactele nereconstruite ABSENTE, nu false)', async () => {
|
||||||
|
cere(!attJ.eroare, attJ.eroare);
|
||||||
|
const r = await verify(attJ, [BIN, S], { rebuildFrom: C });
|
||||||
|
const sb = check(r, /attested SBOM says what the committed lockfile.json \(maven-lockfile\) resolves \(617 components\)/), art = check(r, /^rebuild: artifacts$/);
|
||||||
|
cere(r.valid && sb && sb.pass === true && art && art.pass === null, JSON.stringify(r.checks.filter((c) => c.pass !== true)));
|
||||||
|
});
|
||||||
|
async function editat(nume, schimba) {
|
||||||
|
const bom = JSON.parse(fs.readFileSync(S, 'utf8')); schimba(bom);
|
||||||
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-mv-e-')); const f = path.join(d, 'x.sbom.cdx.json'); fs.writeFileSync(f, JSON.stringify(bom, null, 1));
|
||||||
|
const a = await attest({ artifacts: [BIN], sbom: f, name: 'maven-lockfile', sourcePath: 'java', cwd: G });
|
||||||
|
const simplu = await verify(a, [BIN, f]);
|
||||||
|
const r = await verify(a, [BIN, f], { rebuildFrom: C });
|
||||||
|
fs.rmSync(d, { recursive: true, force: true });
|
||||||
|
cere(simplu.valid, `${nume}: fara reconstructie, digestul singur trece (de asta conteaza comparatia)`);
|
||||||
|
const sb = check(r, /attested SBOM says/);
|
||||||
|
cere(!r.valid && sb && sb.pass === false, `${nume}: trebuia prins; ${JSON.stringify(sb)}`);
|
||||||
|
}
|
||||||
|
const comp = (b, purl) => b.components.find((c) => c.purl === purl);
|
||||||
|
await test('CONTROL: o componenta scoasa din SBOM de mana, re-atestat -> prins', () => editat('scos', (b) => { b.components = b.components.filter((c) => c.name !== 'gson'); }));
|
||||||
|
await test('CONTROL: o versiune schimbata (si purl-ul) -> prins', () => editat('versiune', (b) => { const c = comp(b, 'pkg:maven/com.google.code.gson/gson@2.14.0'); c.version = '2.14.1'; c.purl = c['bom-ref'] = 'pkg:maven/com.google.code.gson/gson@2.14.1'; }));
|
||||||
|
await test('CONTROL: un SHA-256 schimbat -> prins', () => editat('hash', (b) => { comp(b, 'pkg:maven/com.google.code.gson/gson@2.14.0').hashes[0].content = '0'.repeat(64); }));
|
||||||
|
await test('CONTROL: radacina schimbata -> prins', () => editat('radacina', (b) => { b.metadata.component.name = 'altul'; b.metadata.component.purl = b.metadata.component['bom-ref'] = 'pkg:maven/io.github.chains-project/altul@5.18.7-SNAPSHOT'; }));
|
||||||
|
await test('CONTROL: o muchie scoasa din graf -> prins', () => editat('graf', (b) => { const d = b.dependencies.find((x) => x.ref === b.metadata.component['bom-ref']); d.dependsOn = d.dependsOn.slice(1); }));
|
||||||
|
await test('CONTROL: scope-ul unei componente schimbat (compile -> test) -> prins', () => editat('scope', (b) => { comp(b, 'pkg:maven/com.google.code.gson/gson@2.14.0').properties.find((p) => p.name === 'aere:maven-scope').value = 'test'; }));
|
||||||
|
await test('CONTROL: un plugin scos -> prins', () => editat('plugin', (b) => { b.components = b.components.filter((c) => c.name !== 'spotless-maven-plugin'); }));
|
||||||
|
await test('CONTROL: "pom.xml matches the lockfile" rescris de mana -> prins', () => editat('pom', (b) => { b.metadata.properties.find((p) => p.name === 'aere:maven-pom-matches-lockfile').value = 'false'; }));
|
||||||
|
await test('B-21: un SBOM Maven editat, fara aere:derived-from -> nejudecat, cu motivul care numeste lockfile.json si sbom-maven (nu "nu se poate re-deriva")', async () => {
|
||||||
|
const bom = JSON.parse(fs.readFileSync(S, 'utf8'));
|
||||||
|
bom.components = bom.components.filter((c) => c.name !== 'gson');
|
||||||
|
bom.metadata.properties = bom.metadata.properties.filter((p) => p.name !== 'aere:derived-from');
|
||||||
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-mv-e-')); const f = path.join(d, 'x.sbom.cdx.json'); fs.writeFileSync(f, JSON.stringify(bom, null, 1));
|
||||||
|
const a = await attest({ artifacts: [BIN], sbom: f, name: 'maven-lockfile', sourcePath: 'java', cwd: G });
|
||||||
|
const r = await verify(a, [BIN, f], { rebuildFrom: C }); fs.rmSync(d, { recursive: true, force: true });
|
||||||
|
const sb = check(r, /^rebuild: SBOM$/);
|
||||||
|
cere(sb && sb.pass === null && /names no lockfile/.test(sb.detail) && /it has lockfile\.json/.test(sb.detail) && /sbom-maven/.test(sb.detail), JSON.stringify(sb));
|
||||||
|
});
|
||||||
|
await test('modulele: pom-ul agregator fara lockfile refuza pe nume si numeste modulele; un pom cu <modules> si lockfile spune care module au lockfile.json comis; lockfile fara pom.xml, sau niciun Maven -> spus, nu trecut', () => {
|
||||||
|
const a = sbomMavenFromTree(G, HEAD, 'reactor', '1'), b = sbomMavenFromTree(G, HEAD, 'fara-pom', '1'), c = sbomMavenFromTree(G, HEAD, 'nimic', '1'), d = sbomMavenFromTree(G, HEAD, 'cu-module', null);
|
||||||
|
cere(/aggregator with modules module-b, module-a/.test(a.lipsa || '') && /per module/.test(a.lipsa || ''), JSON.stringify(a).slice(0, 200));
|
||||||
|
cere(/no pom.xml next to it/.test(b.lipsa || '') && /no lockfile.json/.test(c.lipsa || '') && /maven-lockfile plugin/.test(c.lipsa || ''), [b.lipsa, c.lipsa].join(' | '));
|
||||||
|
cere(d.bom && prop(d.bom.metadata, 'aere:maven-modules') === 'module-b,module-a' && prop(d.bom.metadata, 'aere:maven-modules-with-lockfile') === 'module-a' && prop(d.bom.metadata, 'aere:maven-packaging') === 'pom', JSON.stringify(d.lipsa || d.bom.metadata.properties));
|
||||||
|
const ma = sbomMavenFromTree(G, HEAD, 'reactor/module-a', null);
|
||||||
|
cere(ma.bom && ma.bom.metadata.component.purl === 'pkg:maven/com.mycompany.app/module-a@1.0.0-SNAPSHOT' && prop(ma.bom.metadata, 'aere:maven-modules') === 'none' && rol(ma.bom, 'parent-pom').length === 1, JSON.stringify(ma.lipsa || ma.bom.metadata));
|
||||||
|
});
|
||||||
|
for (const d of [G, C, OUT]) fs.rmSync(d, { recursive: true, force: true });
|
||||||
|
console.log(`\n${treceri} treceri, ${esecuri.length} esecuri`);
|
||||||
|
process.exitCode = esecuri.length ? 1 : 0;
|
||||||
Loading…
Reference in New Issue
Block a user