Stranger pass of 21 Oct: .gitignore for node_modules and the keys the examples write, Maven and Action rows in the table, one install line, temporary directories removed by the tests
This commit is contained in:
parent
c1909c2afb
commit
c3b3d99640
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
node_modules/
|
||||||
|
# Private keys written by the examples of tools/proof-of-software/README.md and its action/README.md
|
||||||
|
keys.json
|
||||||
|
org.json
|
||||||
|
dev.json
|
||||||
@ -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-18, of every path cited in this
|
tree) and enforced by legaturi.cjs. It is the complete list, measured on 2026-10-21, 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,12 +12,14 @@ 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: **7**.
|
Unresolvable distinct paths in this repository: **9**.
|
||||||
|
|
||||||
- `LockFileFacade.java` cited in: aere-proof-of-software/tools/proof-of-software/test/fixturi-maven/README.md
|
- `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
|
||||||
|
- `dev.json` cited in: aere-proof-of-software/README.md, 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
|
||||||
|
- `keys.json` cited in: aere-proof-of-software/README.md, 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/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
|
- `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
|
- `test/sbom-maven.test.mjs` cited in: aere-proof-of-software/tools/proof-of-software/test/fixturi-maven/README.md
|
||||||
|
|||||||
21
README.md
21
README.md
@ -15,25 +15,30 @@ dependencies by relative path, and nothing was rewritten for publication:
|
|||||||
## Install and check
|
## Install and check
|
||||||
|
|
||||||
```
|
```
|
||||||
cd sdk-pq-sign && npm ci --ignore-scripts && cd .. # the pinned @noble dependencies, integrity-checked
|
cd sdk-pq-sign && npm ci --ignore-scripts && cd ../tools/proof-of-software # the pinned @noble dependencies, integrity-checked
|
||||||
cd tools/proof-of-software
|
node test/pos.test.mjs # and the other tests below
|
||||||
node pos.mjs keygen --out keys.json
|
|
||||||
node test/pos.test.mjs # and the other tests below
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Node.js 22 or later, and git. Results measured on 2026-09-30 (Node.js 24.14.1, Windows; git 2.x, npm 11):
|
The same line is in `tools/proof-of-software/README.md`. The examples there write private keys (`keys.json`, `org.json`,
|
||||||
|
`dev.json`); the repository's `.gitignore` ignores those names and `node_modules/`, so a `git add -A` in a clone where the examples
|
||||||
|
were run does not pick them up.
|
||||||
|
|
||||||
|
Node.js 22 or later, and git. Results measured on 2026-09-30 (Node.js 24.14.1, Windows; git 2.x, npm 11), except where a row gives
|
||||||
|
another date; on 2026-10-21 every row was measured again from a fresh clone of this repository, alone in an empty folder, with the
|
||||||
|
same figures:
|
||||||
|
|
||||||
| test | result | negative control |
|
| test | result | negative control |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| attestation and verification | 16/16 (`tools/proof-of-software/test/pos.test.mjs`) | inside the test |
|
| attestation and verification | 16/16 (`tools/proof-of-software/test/pos.test.mjs`) | inside the test |
|
||||||
| ML-BOM of a model directory | 8/8 (`tools/proof-of-software/test/model.test.mjs`) | `tools/proof-of-software/test/model-control-negativ.mjs` edits `pos.mjs` in place and restores it; not run for this table |
|
| ML-BOM of a model directory | 8/8 (`tools/proof-of-software/test/model.test.mjs`) | 4/4 on 2026-10-21 (`tools/proof-of-software/test/model-control-negativ.mjs`; it edits `pos.mjs` in place and restores it, so run it on a clean tree) |
|
||||||
| developer credential | 12/12 (`tools/proof-of-software/test/credential.test.mjs`) | 7/7 (`tools/proof-of-software/test/credential-control-negativ.mjs`) |
|
| developer credential | 12/12 (`tools/proof-of-software/test/credential.test.mjs`) | 7/7 (`tools/proof-of-software/test/credential-control-negativ.mjs`) |
|
||||||
| who signed (`--signer`) | 7/7 (`tools/proof-of-software/test/semnatar.test.mjs`) | 4/4 (`tools/proof-of-software/test/semnatar-control-negativ.mjs`) |
|
| who signed (`--signer`) | 7/7 (`tools/proof-of-software/test/semnatar.test.mjs`) | 4/4 (`tools/proof-of-software/test/semnatar-control-negativ.mjs`) |
|
||||||
| npm SBOM from the committed lockfile | 11/11 (`tools/proof-of-software/test/sbom.test.mjs`) | 5/5 (`tools/proof-of-software/test/sbom-control-negativ.mjs`) |
|
| npm SBOM from the committed lockfile | 11/11 (`tools/proof-of-software/test/sbom.test.mjs`) | 5/5 (`tools/proof-of-software/test/sbom-control-negativ.mjs`) |
|
||||||
| Go SBOM from the committed go.sum | 14/14 (`tools/proof-of-software/test/sbom-go.test.mjs`) | 8/8 (`tools/proof-of-software/test/sbom-go-control-negativ.mjs`) |
|
| Go SBOM from the committed go.sum | 14/14 (`tools/proof-of-software/test/sbom-go.test.mjs`) | 8/8 (`tools/proof-of-software/test/sbom-go-control-negativ.mjs`) |
|
||||||
| .NET and Gradle SBOMs from the committed lock files | 27/27 (`tools/proof-of-software/test/sbom-nuget-gradle.test.mjs`) | 20/20 (`tools/proof-of-software/test/sbom-nuget-gradle-control-negativ.mjs`) |
|
| .NET and Gradle SBOMs from the committed lock files | 27/27 (`tools/proof-of-software/test/sbom-nuget-gradle.test.mjs`) | 20/20 (`tools/proof-of-software/test/sbom-nuget-gradle-control-negativ.mjs`) |
|
||||||
|
| Maven SBOM from the committed lockfile.json (1.6.0) | 26/26 on 2026-10-18 and 2026-10-21 (`tools/proof-of-software/test/sbom-maven.test.mjs`) | 16/16 (`tools/proof-of-software/test/sbom-maven-control-negativ.mjs`) |
|
||||||
| hybrid signature library | 34/34 (`sdk-pq-sign/test/pq-sign.test.mjs`); its check of the ML-DSA half on chain is skipped without a Cloud key | inside the test |
|
| hybrid signature library | 34/34 (`sdk-pq-sign/test/pq-sign.test.mjs`); its check of the ML-DSA half on chain is skipped without a Cloud key | inside the test |
|
||||||
| GitHub Action | `tools/proof-of-software/test/action.test.mjs` (long; installs pinned dependencies from the npm registry) | not measured for this publication |
|
| GitHub Action | 25/25 on 2026-10-21 (`tools/proof-of-software/test/action.test.mjs`; long: it installs pinned dependencies from the npm registry) | on 2026-10-21 (`tools/proof-of-software/test/action-dovada.mjs`, on a copy of the action, one group of guards at a time, each taken out by a condition false at run time): the eight guards in three groups (4, 2, 2), each turning exactly its own tests red and every other test of the suite green; the verdict line is `DOVADA CONTROALELOR NEGATIVE: VERDE` |
|
||||||
|
|
||||||
The GitHub Action is in this repository, which lives on git.aere.network; GitHub runs actions only from GitHub repositories, so
|
The GitHub Action is in this repository, which lives on git.aere.network; GitHub runs actions only from GitHub repositories, so
|
||||||
to use it, copy these three folders into a GitHub repository you control and point `uses:` at it, pinned to a full commit SHA
|
to use it, copy these three folders into a GitHub repository you control and point `uses:` at it, pinned to a full commit SHA
|
||||||
@ -44,4 +49,4 @@ the documentation are in English. No third party has reviewed this code.
|
|||||||
|
|
||||||
## Licence
|
## Licence
|
||||||
|
|
||||||
MIT, see [LICENSE](LICENSE). Files: 61 (tools/proof-of-software 48, sdk-pq-sign 6, sdk 3).
|
MIT, see [LICENSE](LICENSE). Files: 62 (tools/proof-of-software 48, sdk-pq-sign 6, sdk 3).
|
||||||
|
|||||||
@ -306,10 +306,13 @@ node test/sbom-maven-control-negativ.mjs # on a copy, 16 guards removed -> the
|
|||||||
dependencies come from its lockfile. From a fresh clone of the published repository, install those first, then run the tests:
|
dependencies come from its lockfile. From a fresh clone of the published repository, install those first, then run the tests:
|
||||||
|
|
||||||
```
|
```
|
||||||
cd sdk-pq-sign && npm ci && cd ../tools/proof-of-software
|
cd sdk-pq-sign && npm ci --ignore-scripts && cd ../tools/proof-of-software # the pinned @noble dependencies, integrity-checked
|
||||||
node test/pos.test.mjs # and the others listed above
|
node test/pos.test.mjs # and the others listed above
|
||||||
```
|
```
|
||||||
|
|
||||||
Measured on 2026-10-18 from a fresh clone on Windows: without that step every test stops at `Cannot find package '@noble/post-quantum'`;
|
Measured on 2026-10-18 from a fresh clone on Windows: without that step every test stops at `Cannot find package '@noble/post-quantum'`;
|
||||||
with it, 16/16, 11/11, 14/14, 27/27, 26/26, 8/8, 12/12, 7/7. The repository carries `.gitattributes` (`* text=auto eol=lf`) so that a
|
with it, 16/16, 11/11, 14/14, 27/27, 26/26, 8/8, 12/12, 7/7. The repository carries `.gitattributes` (`* text=auto eol=lf`) so that a
|
||||||
checkout keeps the committed bytes of the SBOM fixtures on every platform (a CRLF checkout changes the SHA-256 of a committed `pom.xml`).
|
checkout keeps the committed bytes of the SBOM fixtures on every platform (a CRLF checkout changes the SHA-256 of a committed `pom.xml`).
|
||||||
|
Measured again on 2026-10-21 from a fresh clone, the same figures. Since that day the repository also carries a `.gitignore` for
|
||||||
|
`node_modules/` and the private keys the examples above write (`keys.json`, `org.json`, `dev.json`): before it, running the examples in a
|
||||||
|
clone left the signing key as an untracked file that a `git add -A` would have committed.
|
||||||
|
|||||||
@ -7,11 +7,16 @@ import path from 'node:path';
|
|||||||
import { execFileSync } from 'node:child_process';
|
import { execFileSync } from 'node:child_process';
|
||||||
import { attest, verify, buildStatement, packNpmFromTree } from '../pos.mjs';
|
import { attest, verify, buildStatement, packNpmFromTree } from '../pos.mjs';
|
||||||
import * as pq from '../../../sdk-pq-sign/index.mjs';
|
import * as pq from '../../../sdk-pq-sign/index.mjs';
|
||||||
|
// 2026-10-21 (the stranger pass): every temporary directory of this test is remembered and removed when the process exits, on every
|
||||||
|
// path (the end, an exception, process.exit); until then each run left its directories in TEMP
|
||||||
|
const facute = [];
|
||||||
|
const tmp = (p) => { const d = fs.mkdtempSync(path.join(os.tmpdir(), p)); facute.push(d); return d; };
|
||||||
|
process.on('exit', () => { for (const d of facute) { try { fs.rmSync(d, { recursive: true, force: true, maxRetries: 3 }); } catch { /* left for the system */ } } });
|
||||||
|
|
||||||
let treceri = 0; const esecuri = [];
|
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)); } }
|
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 D = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-'));
|
const D = tmp('aere-pos-');
|
||||||
const A = path.join(D, 'app.tgz'), B = path.join(D, 'app.sha'), S = path.join(D, 'sbom.json');
|
const A = path.join(D, 'app.tgz'), B = path.join(D, 'app.sha'), S = path.join(D, 'sbom.json');
|
||||||
fs.writeFileSync(A, Buffer.from('binary artifact bytes 12345')); fs.writeFileSync(B, 'checksums'); fs.writeFileSync(S, JSON.stringify({ bomFormat: 'CycloneDX', components: [] }));
|
fs.writeFileSync(A, Buffer.from('binary artifact bytes 12345')); fs.writeFileSync(B, 'checksums'); fs.writeFileSync(S, JSON.stringify({ bomFormat: 'CycloneDX', components: [] }));
|
||||||
const keys = pq.generateKeyPair({ alg: 'secp256k1+ml-dsa-65' });
|
const keys = pq.generateKeyPair({ alg: 'secp256k1+ml-dsa-65' });
|
||||||
@ -22,7 +27,7 @@ await test('statement: artifacts with sha256 + size, sbom hashed, builder, time'
|
|||||||
assert.strictEqual(s.sbom.name, 'sbom.json'); assert.strictEqual(s.subject.version, '1.0.0'); assert.ok(s.createdAt);
|
assert.strictEqual(s.sbom.name, 'sbom.json'); assert.strictEqual(s.subject.version, '1.0.0'); assert.ok(s.createdAt);
|
||||||
});
|
});
|
||||||
await test('two artifacts with the same name are refused (verification matches by name)', () => {
|
await test('two artifacts with the same name are refused (verification matches by name)', () => {
|
||||||
const D2 = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-')); fs.mkdirSync(path.join(D2, 'x')); fs.writeFileSync(path.join(D2, 'x', 'app.tgz'), 'a');
|
const D2 = tmp('aere-pos-'); fs.mkdirSync(path.join(D2, 'x')); fs.writeFileSync(path.join(D2, 'x', 'app.tgz'), 'a');
|
||||||
assert.throws(() => buildStatement({ artifacts: [A, path.join(D2, 'x', 'app.tgz')], cwd: D }), /share the name/);
|
assert.throws(() => buildStatement({ artifacts: [A, path.join(D2, 'x', 'app.tgz')], cwd: D }), /share the name/);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -69,14 +74,14 @@ await test('CLI: keygen, attest, verify round trip through files; tampered file
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ---- legarea de ARBORELE git si verificarea prin RECONSTRUCTIE (1.1.0). Un depozit de proba cu un pachet npm mic.
|
// ---- legarea de ARBORELE git si verificarea prin RECONSTRUCTIE (1.1.0). Un depozit de proba cu un pachet npm mic.
|
||||||
const G = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-git-'));
|
const G = tmp('aere-pos-git-');
|
||||||
const g = (args, cwd = G) => execFileSync('git', ['-c', 'core.autocrlf=false', '-c', 'user.name=proba', '-c', 'user.email=proba@example.invalid', ...args], { cwd, stdio: 'pipe' }).toString().trim();
|
const g = (args, cwd = G) => execFileSync('git', ['-c', 'core.autocrlf=false', '-c', 'user.name=proba', '-c', 'user.email=proba@example.invalid', ...args], { cwd, stdio: 'pipe' }).toString().trim();
|
||||||
g(['init', '-q']); fs.mkdirSync(path.join(G, 'pkg', 'lib'), { recursive: true }); fs.mkdirSync(path.join(G, 'altceva'));
|
g(['init', '-q']); fs.mkdirSync(path.join(G, 'pkg', 'lib'), { recursive: true }); fs.mkdirSync(path.join(G, 'altceva'));
|
||||||
fs.writeFileSync(path.join(G, 'pkg', 'package.json'), JSON.stringify({ name: 'proba-pos', version: '0.0.1', main: 'lib/index.js', files: ['lib', 'LICENSE'] }, null, 1) + '\n');
|
fs.writeFileSync(path.join(G, 'pkg', 'package.json'), JSON.stringify({ name: 'proba-pos', version: '0.0.1', main: 'lib/index.js', files: ['lib', 'LICENSE'] }, null, 1) + '\n');
|
||||||
fs.writeFileSync(path.join(G, 'pkg', 'lib', 'index.js'), 'module.exports = 1;\n'); fs.writeFileSync(path.join(G, 'pkg', 'LICENSE'), 'line one\nline two\n');
|
fs.writeFileSync(path.join(G, 'pkg', 'lib', 'index.js'), 'module.exports = 1;\n'); fs.writeFileSync(path.join(G, 'pkg', 'LICENSE'), 'line one\nline two\n');
|
||||||
fs.writeFileSync(path.join(G, 'altceva', 'x.txt'), 'nu face parte din pachet\n');
|
fs.writeFileSync(path.join(G, 'altceva', 'x.txt'), 'nu face parte din pachet\n');
|
||||||
g(['add', '-A']); g(['commit', '-q', '-m', 'pachet de proba']);
|
g(['add', '-A']); g(['commit', '-q', '-m', 'pachet de proba']);
|
||||||
const OUT1 = path.join(G, '..', path.basename(G) + '-out'); fs.mkdirSync(OUT1);
|
const OUT1 = path.join(G, '..', path.basename(G) + '-out'); fs.mkdirSync(OUT1); facute.push(OUT1);
|
||||||
const TGZ = packNpmFromTree(G, 'HEAD:pkg', OUT1);
|
const TGZ = packNpmFromTree(G, 'HEAD:pkg', OUT1);
|
||||||
|
|
||||||
await test('source tree: --source-path records path, tree and pathDirty; build records npm-pack and the npm version', () => {
|
await test('source tree: --source-path records path, tree and pathDirty; build records npm-pack and the npm version', () => {
|
||||||
@ -86,13 +91,13 @@ await test('source tree: --source-path records path, tree and pathDirty; build r
|
|||||||
assert.throws(() => buildStatement({ artifacts: [TGZ], sourcePath: 'nu-exista', cwd: G }), /not tracked at HEAD/);
|
assert.throws(() => buildStatement({ artifacts: [TGZ], sourcePath: 'nu-exista', cwd: G }), /not tracked at HEAD/);
|
||||||
});
|
});
|
||||||
await test('the listing of a tree does not depend on the directory it is asked from (ls-tree --full-tree)', () => {
|
await test('the listing of a tree does not depend on the directory it is asked from (ls-tree --full-tree)', () => {
|
||||||
const o = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-sub-'));
|
const o = tmp('aere-pos-sub-');
|
||||||
const t2 = packNpmFromTree(path.join(G, 'altceva'), 'HEAD:pkg', o);
|
const t2 = packNpmFromTree(path.join(G, 'altceva'), 'HEAD:pkg', o);
|
||||||
assert.strictEqual(fs.readFileSync(t2).equals(fs.readFileSync(TGZ)), true, 'same tree asked from a subdirectory gave other bytes');
|
assert.strictEqual(fs.readFileSync(t2).equals(fs.readFileSync(TGZ)), true, 'same tree asked from a subdirectory gave other bytes');
|
||||||
});
|
});
|
||||||
await test('the artifact is a function of the COMMITTED tree: CRLF in the working copy changes a working-copy pack, not this one', () => {
|
await test('the artifact is a function of the COMMITTED tree: CRLF in the working copy changes a working-copy pack, not this one', () => {
|
||||||
fs.writeFileSync(path.join(G, 'pkg', 'LICENSE'), 'line one\r\nline two\r\n');
|
fs.writeFileSync(path.join(G, 'pkg', 'LICENSE'), 'line one\r\nline two\r\n');
|
||||||
const o = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-crlf-'));
|
const o = tmp('aere-pos-crlf-');
|
||||||
const again = packNpmFromTree(G, 'HEAD:pkg', o);
|
const again = packNpmFromTree(G, 'HEAD:pkg', o);
|
||||||
assert.strictEqual(fs.readFileSync(again).equals(fs.readFileSync(TGZ)), true);
|
assert.strictEqual(fs.readFileSync(again).equals(fs.readFileSync(TGZ)), true);
|
||||||
const wc = execFileSync('npm pack --silent --pack-destination "' + o + '"', { cwd: path.join(G, 'pkg'), shell: true, stdio: 'pipe' }).toString().trim().split(/\r?\n/).pop();
|
const wc = execFileSync('npm pack --silent --pack-destination "' + o + '"', { cwd: path.join(G, 'pkg'), shell: true, stdio: 'pipe' }).toString().trim().split(/\r?\n/).pop();
|
||||||
@ -102,7 +107,7 @@ await test('the artifact is a function of the COMMITTED tree: CRLF in the workin
|
|||||||
});
|
});
|
||||||
const attG = await attest({ artifacts: [TGZ], name: 'proba-pos', version: '0.0.1', sourcePath: 'pkg', build: 'npm-pack', keys, cwd: G });
|
const attG = await attest({ artifacts: [TGZ], name: 'proba-pos', version: '0.0.1', sourcePath: 'pkg', build: 'npm-pack', keys, cwd: G });
|
||||||
await test('verify --rebuild-from: a clone chosen by the verifier reproduces the attested artifact byte for byte', async () => {
|
await test('verify --rebuild-from: a clone chosen by the verifier reproduces the attested artifact byte for byte', async () => {
|
||||||
const C = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-clona-')); g(['clone', '-q', G, C], os.tmpdir());
|
const C = tmp('aere-pos-clona-'); g(['clone', '-q', G, C], os.tmpdir());
|
||||||
const r = await verify(attG, [TGZ], { rebuildFrom: C });
|
const r = await verify(attG, [TGZ], { rebuildFrom: C });
|
||||||
assert.strictEqual(r.valid, true, JSON.stringify(r.checks));
|
assert.strictEqual(r.valid, true, JSON.stringify(r.checks));
|
||||||
assert.ok(r.checks.find((c) => c.name.startsWith('rebuild: npm pack of the attested tree reproduces') && c.pass === true));
|
assert.ok(r.checks.find((c) => c.name.startsWith('rebuild: npm pack of the attested tree reproduces') && c.pass === true));
|
||||||
@ -110,7 +115,7 @@ await test('verify --rebuild-from: a clone chosen by the verifier reproduces the
|
|||||||
});
|
});
|
||||||
await test('an artifact that did NOT come from the attested tree fails the rebuild (digest differs), though its own digest matches', async () => {
|
await test('an artifact that did NOT come from the attested tree fails the rebuild (digest differs), though its own digest matches', async () => {
|
||||||
fs.writeFileSync(path.join(G, 'pkg', 'lib', 'index.js'), 'module.exports = 2; // schimbat si necomis\n');
|
fs.writeFileSync(path.join(G, 'pkg', 'lib', 'index.js'), 'module.exports = 2; // schimbat si necomis\n');
|
||||||
const o = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-strain-'));
|
const o = tmp('aere-pos-strain-');
|
||||||
const wc = execFileSync('npm pack --silent --pack-destination "' + o + '"', { cwd: path.join(G, 'pkg'), shell: true, stdio: 'pipe' }).toString().trim().split(/\r?\n/).pop();
|
const wc = execFileSync('npm pack --silent --pack-destination "' + o + '"', { cwd: path.join(G, 'pkg'), shell: true, stdio: 'pipe' }).toString().trim().split(/\r?\n/).pop();
|
||||||
const strain = path.join(o, wc);
|
const strain = path.join(o, wc);
|
||||||
const a = await attest({ artifacts: [strain], sourcePath: 'pkg', build: 'npm-pack', cwd: G });
|
const a = await attest({ artifacts: [strain], sourcePath: 'pkg', build: 'npm-pack', cwd: G });
|
||||||
@ -121,7 +126,7 @@ await test('an artifact that did NOT come from the attested tree fails the rebui
|
|||||||
g(['checkout', '-q', '--', 'pkg/lib/index.js']);
|
g(['checkout', '-q', '--', 'pkg/lib/index.js']);
|
||||||
});
|
});
|
||||||
await test('a clone that does not hold the attested commit fails on the SOURCE check, before any build', async () => {
|
await test('a clone that does not hold the attested commit fails on the SOURCE check, before any build', async () => {
|
||||||
const E = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-gol-')); g(['init', '-q'], E); fs.writeFileSync(path.join(E, 'a'), 'a'); g(['add', '-A'], E); g(['commit', '-q', '-m', 'alt depozit'], E);
|
const E = tmp('aere-pos-gol-'); g(['init', '-q'], E); fs.writeFileSync(path.join(E, 'a'), 'a'); g(['add', '-A'], E); g(['commit', '-q', '-m', 'alt depozit'], E);
|
||||||
const r = await verify(attG, [TGZ], { rebuildFrom: E }); assert.strictEqual(r.valid, false);
|
const r = await verify(attG, [TGZ], { rebuildFrom: E }); assert.strictEqual(r.valid, false);
|
||||||
assert.ok(r.checks.find((c) => c.name.startsWith('source:') && c.pass === false && /not in this clone/.test(c.detail)));
|
assert.ok(r.checks.find((c) => c.name.startsWith('source:') && c.pass === false && /not in this clone/.test(c.detail)));
|
||||||
assert.ok(!r.checks.find((c) => c.name.startsWith('rebuild: npm pack')), 'no build was attempted');
|
assert.ok(!r.checks.find((c) => c.name.startsWith('rebuild: npm pack')), 'no build was attempted');
|
||||||
|
|||||||
@ -7,6 +7,11 @@ import os from 'node:os';
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { execFileSync } from 'node:child_process';
|
import { execFileSync } from 'node:child_process';
|
||||||
import { attest, verify, packNpmFromTree, sbomSemantica, sbomNpmFromTree } from '../pos.mjs';
|
import { attest, verify, packNpmFromTree, sbomSemantica, sbomNpmFromTree } from '../pos.mjs';
|
||||||
|
// 2026-10-21 (the stranger pass): every temporary directory of this test is remembered and removed when the process exits, on every
|
||||||
|
// path (the end, an exception, process.exit); until then each run left its directories in TEMP
|
||||||
|
const facute = [];
|
||||||
|
const tmp = (p) => { const d = fs.mkdtempSync(path.join(os.tmpdir(), p)); facute.push(d); return d; };
|
||||||
|
process.on('exit', () => { for (const d of facute) { try { fs.rmSync(d, { recursive: true, force: true, maxRetries: 3 }); } catch { /* left for the system */ } } });
|
||||||
|
|
||||||
let treceri = 0; const esecuri = [];
|
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)); } }
|
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)); } }
|
||||||
@ -14,7 +19,7 @@ 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 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 check = (r, re) => r.checks.find((c) => re.test(c.name));
|
||||||
|
|
||||||
const G = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-sbom-g-'));
|
const G = tmp('aere-pos-sbom-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);
|
g(['init', '-q'], G); g(['config', 'user.email', 'proba@aere.invalid'], G); g(['config', 'user.name', 'proba'], G); g(['config', 'core.autocrlf', 'false'], G);
|
||||||
const P = path.join(G, 'pkg'); fs.mkdirSync(path.join(P, 'lib'), { recursive: true });
|
const P = path.join(G, 'pkg'); fs.mkdirSync(path.join(P, 'lib'), { recursive: true });
|
||||||
fs.writeFileSync(path.join(P, 'package.json'), JSON.stringify({ name: 'proba-sbom', version: '0.1.0', license: 'MIT', main: 'lib/index.js', dependencies: { 'dep-a': '1.0.0', 'dep-b': '2.0.0' } }, null, 2) + '\n');
|
fs.writeFileSync(path.join(P, 'package.json'), JSON.stringify({ name: 'proba-sbom', version: '0.1.0', license: 'MIT', main: 'lib/index.js', dependencies: { 'dep-a': '1.0.0', 'dep-b': '2.0.0' } }, null, 2) + '\n');
|
||||||
@ -34,9 +39,9 @@ fs.writeFileSync(path.join(P, 'package-lock.json'), JSON.stringify(lock, null, 2
|
|||||||
fs.mkdirSync(path.join(G, 'fara'), { recursive: true });
|
fs.mkdirSync(path.join(G, 'fara'), { recursive: true });
|
||||||
fs.writeFileSync(path.join(G, 'fara', 'package.json'), JSON.stringify({ name: 'fara-lock', version: '0.0.1', license: 'MIT' }) + '\n');
|
fs.writeFileSync(path.join(G, 'fara', 'package.json'), JSON.stringify({ name: 'fara-lock', version: '0.0.1', license: 'MIT' }) + '\n');
|
||||||
g(['add', '-A'], G); g(['commit', '-q', '-m', 'pachet de proba'], G);
|
g(['add', '-A'], G); g(['commit', '-q', '-m', 'pachet de proba'], G);
|
||||||
const C = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-sbom-c-')); g(['clone', '-q', G, C], os.tmpdir());
|
const C = tmp('aere-pos-sbom-c-'); g(['clone', '-q', G, C], os.tmpdir());
|
||||||
|
|
||||||
const OUT = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-sbom-o-'));
|
const OUT = tmp('aere-pos-sbom-o-');
|
||||||
const TGZ = packNpmFromTree(G, 'HEAD:pkg', OUT);
|
const TGZ = packNpmFromTree(G, 'HEAD:pkg', OUT);
|
||||||
const scrieSbom = (nume, bom) => { const f = path.join(OUT, nume); fs.writeFileSync(f, JSON.stringify(bom, null, 2)); return f; };
|
const scrieSbom = (nume, bom) => { const f = path.join(OUT, nume); fs.writeFileSync(f, JSON.stringify(bom, null, 2)); return f; };
|
||||||
const r1 = sbomNpmFromTree(G, 'HEAD:pkg'); const r2 = sbomNpmFromTree(G, 'HEAD:pkg');
|
const r1 = sbomNpmFromTree(G, 'HEAD:pkg'); const r2 = sbomNpmFromTree(G, 'HEAD:pkg');
|
||||||
@ -60,7 +65,7 @@ await test('un SBOM regenerat (alt numar de serie, alt timp) si re-atestat trece
|
|||||||
});
|
});
|
||||||
async function editat(nume, schimba) {
|
async function editat(nume, schimba) {
|
||||||
const bom = JSON.parse(JSON.stringify(r1.bom)); schimba(bom);
|
const bom = JSON.parse(JSON.stringify(r1.bom)); schimba(bom);
|
||||||
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-sbom-e-')); const f = path.join(d, 'proba-sbom.sbom.cdx.json'); fs.writeFileSync(f, JSON.stringify(bom, null, 2));
|
const d = tmp('aere-pos-sbom-e-'); const f = path.join(d, 'proba-sbom.sbom.cdx.json'); fs.writeFileSync(f, JSON.stringify(bom, null, 2));
|
||||||
const a = await attest({ artifacts: [TGZ], sbom: f, name: 'proba-sbom', version: '0.1.0', sourcePath: 'pkg', build: 'npm-pack', cwd: G });
|
const a = await attest({ artifacts: [TGZ], sbom: f, name: 'proba-sbom', version: '0.1.0', sourcePath: 'pkg', build: 'npm-pack', cwd: G });
|
||||||
const simplu = await verify(a, [TGZ, f]);
|
const simplu = await verify(a, [TGZ, f]);
|
||||||
const r = await verify(a, [TGZ, f], { rebuildFrom: C });
|
const r = await verify(a, [TGZ, f], { rebuildFrom: C });
|
||||||
@ -76,7 +81,7 @@ async function deAltFel(din) {
|
|||||||
const bom = JSON.parse(JSON.stringify(r1.bom));
|
const bom = JSON.parse(JSON.stringify(r1.bom));
|
||||||
bom.components = bom.components.filter((c) => !/dep-c/.test(c.name));
|
bom.components = bom.components.filter((c) => !/dep-c/.test(c.name));
|
||||||
bom.metadata.properties = [...(bom.metadata.properties || []), { name: 'aere:derived-from', value: din }];
|
bom.metadata.properties = [...(bom.metadata.properties || []), { name: 'aere:derived-from', value: din }];
|
||||||
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-sbom-e-')); const f = path.join(d, 'proba-sbom.sbom.cdx.json'); fs.writeFileSync(f, JSON.stringify(bom, null, 2));
|
const d = tmp('aere-pos-sbom-e-'); const f = path.join(d, 'proba-sbom.sbom.cdx.json'); fs.writeFileSync(f, JSON.stringify(bom, null, 2));
|
||||||
const a = await attest({ artifacts: [TGZ], sbom: f, name: 'proba-sbom', version: '0.1.0', sourcePath: 'pkg', build: 'npm-pack', cwd: G });
|
const a = await attest({ artifacts: [TGZ], sbom: f, name: 'proba-sbom', version: '0.1.0', sourcePath: 'pkg', build: 'npm-pack', cwd: G });
|
||||||
const r = await verify(a, [TGZ, f], { rebuildFrom: C }); fs.rmSync(d, { recursive: true, force: true });
|
const r = await verify(a, [TGZ, f], { rebuildFrom: C }); fs.rmSync(d, { recursive: true, force: true });
|
||||||
const rand = check(r, /file the attested SBOM says it was derived from is in the committed tree/);
|
const rand = check(r, /file the attested SBOM says it was derived from is in the committed tree/);
|
||||||
@ -88,7 +93,7 @@ await test('CONTROL B-21: un SBOM npm editat care numeste un fisier AL arborelui
|
|||||||
const bom = JSON.parse(JSON.stringify(r1.bom));
|
const bom = JSON.parse(JSON.stringify(r1.bom));
|
||||||
bom.components = bom.components.filter((c) => !/dep-c/.test(c.name));
|
bom.components = bom.components.filter((c) => !/dep-c/.test(c.name));
|
||||||
bom.metadata.properties = [...(bom.metadata.properties || []), { name: 'aere:derived-from', value: 'package.json' }];
|
bom.metadata.properties = [...(bom.metadata.properties || []), { name: 'aere:derived-from', value: 'package.json' }];
|
||||||
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-pos-sbom-e-')); const f = path.join(d, 'proba-sbom.sbom.cdx.json'); fs.writeFileSync(f, JSON.stringify(bom, null, 2));
|
const d = tmp('aere-pos-sbom-e-'); const f = path.join(d, 'proba-sbom.sbom.cdx.json'); fs.writeFileSync(f, JSON.stringify(bom, null, 2));
|
||||||
const a = await attest({ artifacts: [TGZ], sbom: f, name: 'proba-sbom', version: '0.1.0', sourcePath: 'pkg', build: 'npm-pack', cwd: G });
|
const a = await attest({ artifacts: [TGZ], sbom: f, name: 'proba-sbom', version: '0.1.0', sourcePath: 'pkg', build: 'npm-pack', cwd: G });
|
||||||
const r = await verify(a, [TGZ, f], { rebuildFrom: C }); fs.rmSync(d, { recursive: true, force: true });
|
const r = await verify(a, [TGZ, f], { rebuildFrom: C }); fs.rmSync(d, { recursive: true, force: true });
|
||||||
cere(!r.valid && check(r, /attested SBOM says what the committed lockfile says/) && check(r, /attested SBOM says/).pass === false, JSON.stringify(r.checks.filter((c) => c.pass !== true)));
|
cere(!r.valid && check(r, /attested SBOM says what the committed lockfile says/) && check(r, /attested SBOM says/).pass === false, JSON.stringify(r.checks.filter((c) => c.pass !== true)));
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user