mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2026-11-04 11:52:03 +00:00
Add an SBOM, and fix the torch randomness nits (L4, L5)
L5: dependency-audit.yml now generates a CycloneDX SBOM from requirements.lock and uploads it. pip-audit answers "are there known CVEs today"; an SBOM answers "which exact versions were in this build", which is the question that arises when something is disclosed later against a release already in the wild. Verified against the real tool rather than written from memory -- the first flag spelling (--output-format / --outfile) does not exist in cyclonedx-py and would have failed in CI. The working form is `--of json --output-reproducible -o <file>`, which emits CycloneDX 1.6 with 96 components and records [email protected], [email protected], [email protected], [email protected], [email protected], [email protected] and [email protected].0 at exact versions. L4: torch/services/memory/index.js built its temp filename from Date.now() plus Math.random() before an atomic rename -- predictable enough to pre-create or symlink between the write and the rename. Now randomBytes(8). Also renames the Math.random-derived value in relay-health.mjs from `nonce` to `probeSuffix` and draws it from randomBytes(6). It is a public probe tag used to correlate a publish with its readback, not a cryptographic nonce, and nothing was wrong with it -- but the name invited someone to copy the pattern somewhere it would matter. torch has no test/ directory in the repo, so there is no suite to run for these two files; both were syntax-checked and confirmed to import cleanly. The SeedPass determinism gate is green (84 tests). Not done, and recorded as a decision in TODO.md instead: torch/_backups/ holds 5240 tracked files across six dated snapshots. Untracking them is a large deletion and not something to do as a side effect of an entropy audit. They currently duplicate every security-relevant grep hit, and bare `node --test` in torch/ discovers them and fails 18 tests from stale snapshot code. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01HtNMsKzzFTPURemX7tT19u
This commit is contained in:
1 parent
2aed95a687
commit
7047a5dfed
5 files changed
+77
-11
No files matched your search
@@ -25,3 +25,19 @@ jobs:
|
|||||||
pip install --require-hashes -r requirements.lock
|
pip install --require-hashes -r requirements.lock
|
||||||
- name: Run pip-audit
|
- name: Run pip-audit
|
||||||
run: pip-audit -r requirements.lock --ignore-vuln GHSA-wj6h-64fc-37mp --ignore-vuln CVE-2025-62727
|
run: pip-audit -r requirements.lock --ignore-vuln GHSA-wj6h-64fc-37mp --ignore-vuln CVE-2025-62727
|
||||||
|
# Entropy audit L5: pip-audit answers "are there known CVEs today". An
|
||||||
|
# SBOM answers "which exact versions were in this build", which is the
|
||||||
|
# question that arises when a vulnerability is disclosed later against a
|
||||||
|
# release already in the wild.
|
||||||
|
- name: Generate CycloneDX SBOM
|
||||||
|
run: |
|
||||||
|
python -m pip install cyclonedx-bom
|
||||||
|
mkdir -p security-artifacts
|
||||||
|
cyclonedx-py requirements requirements.lock \
|
||||||
|
--of json --output-reproducible \
|
||||||
|
-o security-artifacts/sbom.cdx.json
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: seedpass-sbom
|
||||||
|
path: security-artifacts/
|
||||||
|
if-no-files-found: error
|
||||||
@@ -41,10 +41,18 @@ fixes land behind a new `gen_version` field; v1 output must stay byte-identical.
|
|||||||
site first"). Never bulk-migrate. Until this ships, existing entries stay on v1 by design.
|
site first"). Never bulk-migrate. Until this ships, existing entries stay on v1 by design.
|
||||||
- [ ] **L2** — master seed is `os.urandom(32)` reduced to a hardcoded 12-word (128-bit) mnemonic
|
- [ ] **L2** — master seed is `os.urandom(32)` reduced to a hardcoded 12-word (128-bit) mnemonic
|
||||||
while derived seeds default to 24 words. Not a defect; offer 24 words at profile creation.
|
while derived seeds default to 24 words. Not a defect; offer 24 words at profile creation.
|
||||||
- [ ] **L4** — `torch/`: predictable temp filename (`services/memory/index.js:60`), a `Math.random`
|
- [x] **L4 (code fixes done)** — `torch/` temp filename now uses `randomBytes(8)`; the
|
||||||
value misnamed `nonce` (`relay-health.mjs:95`), and three stale `_backups/` code copies that
|
`Math.random` value misnamed `nonce` in `relay-health.mjs` is now `probeSuffix` from
|
||||||
pollute security greps.
|
`randomBytes(6)`.
|
||||||
- [ ] **L5** — no CycloneDX SBOM; folds into the supply-chain item below.
|
- [ ] **⚠️ Decision needed: `torch/_backups/` holds 5240 tracked files** across six dated
|
||||||
|
snapshots. Untracking is a large deletion, so it is not an audit side effect. Costs today:
|
||||||
|
duplicates every security grep hit, and bare `node --test` in `torch/` discovers them and
|
||||||
|
fails 18 tests from stale snapshot code (`npm test` uses explicit paths, so CI is unaffected).
|
||||||
|
Separately: `torch/` has no `test/` directory in the repo, so its `npm test` cannot run as
|
||||||
|
written.
|
||||||
|
- [x] **L5 (done)** — `dependency-audit.yml` generates and uploads a CycloneDX SBOM from
|
||||||
|
`requirements.lock`. Verified with the real tool: CycloneDX 1.6, 96 components, crypto chain
|
||||||
|
recorded at exact versions.
|
||||||
|
|
||||||
L3 (broad `except Exception` around crypto — these re-raise, so no silent fallback) overlaps the
|
L3 (broad `except Exception` around crypto — these re-raise, so no silent fallback) overlaps the
|
||||||
Robustness item further down.
|
Robustness item further down.
|
||||||
|
|||||||
@@ -352,7 +352,27 @@ should be narrowed to the specific exceptions each path can raise.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### L4 — `torch/` subsystem
|
### L4 — `torch/` subsystem — **PARTLY DONE 2026-07-31**
|
||||||
|
|
||||||
|
Fixed: the temp filename in `services/memory/index.js` now uses
|
||||||
|
`randomBytes(8)` instead of `Date.now()` + `Math.random()`, and the
|
||||||
|
`Math.random`-derived value in `relay-health.mjs` that was called a `nonce` is
|
||||||
|
renamed `probeSuffix` and drawn from `randomBytes(6)` — it is a public probe
|
||||||
|
tag, but the name invited reuse of the pattern somewhere it would matter.
|
||||||
|
|
||||||
|
**Still open, and it needs a decision:** `torch/_backups/` holds **5240 tracked
|
||||||
|
files** across six dated snapshots. Untracking them is a large deletion, so it
|
||||||
|
is not something to do as a side effect of an entropy audit. Two concrete costs
|
||||||
|
today: they duplicate every security-relevant grep hit, and bare `node --test`
|
||||||
|
in `torch/` discovers them and fails 18 tests from stale snapshot code. (The
|
||||||
|
`npm test` script uses explicit paths, so CI is unaffected.)
|
||||||
|
|
||||||
|
Separately noted: `torch/` has **no `test/` directory in the repo at all**, so
|
||||||
|
`npm test` there cannot run as written. Out of scope here, but worth knowing.
|
||||||
|
|
||||||
|
The original note follows.
|
||||||
|
|
||||||
|
### L4 (original) — `torch/` subsystem
|
||||||
|
|
||||||
- `torch/src/services/memory/index.js:60` builds a temp filename from
|
- `torch/src/services/memory/index.js:60` builds a temp filename from
|
||||||
`Date.now()` + `Math.random()` before an atomic rename. Predictable name;
|
`Date.now()` + `Math.random()` before an atomic rename. Predictable name;
|
||||||
@@ -368,7 +388,20 @@ should be narrowed to the specific exceptions each path can raise.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### L5 — No SBOM
|
### L5 — No SBOM — **DONE 2026-07-31**
|
||||||
|
|
||||||
|
`dependency-audit.yml` now generates a CycloneDX SBOM from `requirements.lock`
|
||||||
|
and uploads it. Verified locally with the real tool rather than assumed: the
|
||||||
|
first flag spelling I wrote (`--output-format` / `--outfile`) does not exist in
|
||||||
|
`cyclonedx-py` and would have failed in CI. The working form is
|
||||||
|
`--of json --output-reproducible -o <file>`, which emits CycloneDX 1.6 with 96
|
||||||
|
components and records the crypto chain at exact versions — `[email protected]`,
|
||||||
|
`[email protected]`, `[email protected]`, `[email protected]`, `[email protected]`,
|
||||||
|
`[email protected]`, `[email protected]`.
|
||||||
|
|
||||||
|
The original note follows.
|
||||||
|
|
||||||
|
### L5 (original) — No SBOM
|
||||||
|
|
||||||
`dependency-audit.yml` and `release-integrity.yml` cover part of §11/§12, but
|
`dependency-audit.yml` and `release-integrity.yml` cover part of §11/§12, but
|
||||||
no workflow emits a dependency manifest artifact. Add CycloneDX generation
|
no workflow emits a dependency manifest artifact. Add CycloneDX generation
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { randomBytes } from 'node:crypto';
|
||||||
import fs from 'node:fs/promises';
|
import fs from 'node:fs/promises';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import WebSocket from 'ws';
|
import WebSocket from 'ws';
|
||||||
@@ -92,8 +93,11 @@ async function probePublishRead(relayUrl, namespace, timeoutMs) {
|
|||||||
const sk = generateSecretKey();
|
const sk = generateSecretKey();
|
||||||
const pk = getPublicKey(sk);
|
const pk = getPublicKey(sk);
|
||||||
const createdAt = Math.floor(Date.now() / MS_PER_SECOND);
|
const createdAt = Math.floor(Date.now() / MS_PER_SECOND);
|
||||||
const nonce = `${Date.now()}-${Math.random().toString(16).slice(2, 10)}`;
|
// Named "suffix", not "nonce" (entropy audit L4): this is a public probe tag
|
||||||
const probeTag = `${namespace}-health-probe-${nonce}`;
|
// used to correlate one publish with its readback, not a cryptographic nonce.
|
||||||
|
// The old name invited someone to reuse this pattern where it would matter.
|
||||||
|
const probeSuffix = randomBytes(6).toString('hex');
|
||||||
|
const probeTag = `${namespace}-health-probe-${probeSuffix}`;
|
||||||
const event = finalizeEvent({
|
const event = finalizeEvent({
|
||||||
kind: PROBE_KIND,
|
kind: PROBE_KIND,
|
||||||
created_at: createdAt,
|
created_at: createdAt,
|
||||||
@@ -101,10 +105,10 @@ async function probePublishRead(relayUrl, namespace, timeoutMs) {
|
|||||||
['t', `${namespace}-relay-health`],
|
['t', `${namespace}-relay-health`],
|
||||||
['d', probeTag],
|
['d', probeTag],
|
||||||
],
|
],
|
||||||
content: JSON.stringify({ type: 'relay_health_probe', nonce, namespace, pubkey: pk }),
|
content: JSON.stringify({ type: 'relay_health_probe', probeSuffix, namespace, pubkey: pk }),
|
||||||
}, sk);
|
}, sk);
|
||||||
|
|
||||||
const subId = `probe-${nonce}`;
|
const subId = `probe-${probeSuffix}`;
|
||||||
|
|
||||||
return withTimeout(new Promise((resolve, reject) => {
|
return withTimeout(new Promise((resolve, reject) => {
|
||||||
let okAck = false;
|
let okAck = false;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { randomBytes } from 'node:crypto';
|
||||||
|
|
||||||
import { createMemoryCache } from './cache.js';
|
import { createMemoryCache } from './cache.js';
|
||||||
import { ingestMemoryWindow } from './ingestor.js';
|
import { ingestMemoryWindow } from './ingestor.js';
|
||||||
import { listPruneCandidates, selectPrunableMemories } from './pruner.js';
|
import { listPruneCandidates, selectPrunableMemories } from './pruner.js';
|
||||||
@@ -57,7 +59,10 @@ async function performSave(store) {
|
|||||||
const dir = path.dirname(MEMORY_FILE_PATH);
|
const dir = path.dirname(MEMORY_FILE_PATH);
|
||||||
await fs.promises.mkdir(dir, { recursive: true });
|
await fs.promises.mkdir(dir, { recursive: true });
|
||||||
const entries = [...store.entries()];
|
const entries = [...store.entries()];
|
||||||
const tmpPath = `${MEMORY_FILE_PATH}.${Date.now()}.${Math.random()}.tmp`;
|
// Unguessable temp name (entropy audit L4). Date.now()+Math.random() is
|
||||||
|
// predictable enough to pre-create or symlink from a shared directory
|
||||||
|
// between this line and the rename below.
|
||||||
|
const tmpPath = `${MEMORY_FILE_PATH}.${randomBytes(8).toString('hex')}.tmp`;
|
||||||
await fs.promises.writeFile(tmpPath, JSON.stringify(entries, null, 2), 'utf8');
|
await fs.promises.writeFile(tmpPath, JSON.stringify(entries, null, 2), 'utf8');
|
||||||
await fs.promises.rename(tmpPath, MEMORY_FILE_PATH);
|
await fs.promises.rename(tmpPath, MEMORY_FILE_PATH);
|
||||||
lastSaveTime = Date.now();
|
lastSaveTime = Date.now();
|
||||||
|
|||||||
Reference in new issue
Block a user