103 lines
4.4 KiB
Markdown
103 lines
4.4 KiB
Markdown
# Supply-chain release gate
|
|
|
|
## What is inventoried
|
|
|
|
```text
|
|
Python packages read from the built images, not from a manifest
|
|
Node packages read from the lock file, which is what `npm ci` installs
|
|
base images pinned per component
|
|
CI actions pinned by commit SHA
|
|
runtime images pinned by digest
|
|
```
|
|
|
|
Reading the Python inventory from the image rather than the manifest matters: a manifest states
|
|
intent, an image states what shipped. A build that silently resolved a different transitive version
|
|
is exactly the case an SBOM exists to catch.
|
|
|
|
## SBOM
|
|
|
|
`scripts/m16_sbom.py` produces CycloneDX 1.5 at `docs/security/sbom/modelforge-cyclonedx.json`.
|
|
It lists components and versions with package URLs, not dependency contents: a release inventory
|
|
must be readable and diffable, so no binaries are stored.
|
|
|
|
Each component carries the image it belongs to, so a finding can be traced to what actually ships
|
|
it rather than to the repository as a whole.
|
|
|
|
## Image provenance
|
|
|
|
`docs/security/sbom/image-provenance.json` binds each built image to its image id, repository
|
|
digest where one exists, creation timestamp and the exact source commit. Provenance is what makes
|
|
the SBOM verifiable rather than merely present: without it, an inventory describes an image nobody
|
|
can identify again.
|
|
|
|
## CVE triage
|
|
|
|
Findings are classified, and the classification is evidence rather than a label:
|
|
|
|
```text
|
|
EXPLOITABLE reachable from an untrusted input in this deployment
|
|
NOT_REACHABLE the vulnerable path is not executed by ModelForge
|
|
DEV_ONLY present only in a development or test dependency
|
|
FALSE_POSITIVE the advisory does not apply to this version or configuration
|
|
ACCEPTED_TEMPORARY accepted with an owner, a reason and a review date
|
|
MUST_FIX blocks the release
|
|
```
|
|
|
|
"Zero CVEs" is never claimed without the scan output that supports it, and an aggregate count never
|
|
replaces the per-finding record for a HIGH or CRITICAL.
|
|
|
|
## Dependency pinning
|
|
|
|
Base images are pinned to a major or minor tag so they continue to receive security patches on a
|
|
compatible line; the runtime worker is pinned by digest because its CUDA and PyTorch versions are
|
|
part of the reproducibility contract for a benchmark. CI actions are pinned by commit SHA.
|
|
|
|
No dependency is declared as `latest`. A lock file makes a build reproducible in practice, but a
|
|
floating specifier means the next `npm install` can pull a different major version — or a
|
|
compromised release — without anyone deciding to.
|
|
|
|
## Container posture
|
|
|
|
```text
|
|
non-root api, node-agent, web; postgres and redis drop to their own users
|
|
no privileged mode none
|
|
no Docker socket none
|
|
capabilities dropped entirely on api, node-agent and web
|
|
no-new-privileges api, node-agent, web
|
|
read-only rootfs node-agent, web; api writes only to its mounted data volumes
|
|
healthchecks api, postgres, redis, web
|
|
```
|
|
|
|
## Network boundaries
|
|
|
|
```text
|
|
PostgreSQL loopback only by default
|
|
Redis loopback only by default
|
|
console loopback only by default
|
|
API published deliberately; every admin route is operator-authenticated
|
|
runtime no public port; reached only through the gateway
|
|
Node Agent outbound only; no inbound listener
|
|
```
|
|
|
|
PostgreSQL and Redis defaulting to every interface was the most serious finding of the M16 review:
|
|
the control-plane database, holding provenance, credential hashes and the audit trail, was reachable
|
|
from the LAN behind a development password. Both now bind to loopback, overridable per deployment.
|
|
|
|
## Secrets
|
|
|
|
`gitleaks` runs over the full history, not the working tree. A finding is investigated rather than
|
|
suppressed; the single allowlist entry names one exact string and says why it is not a credential.
|
|
|
|
Generated artefacts are checked too: logs, backup manifests, audit exports and the OpenAPI document
|
|
must contain no operator key, encryption key, node credential or database password.
|
|
|
|
## Cryptography
|
|
|
|
Backup encryption is AES-256-GCM from `cryptography`. ModelForge designs no primitive of its own;
|
|
`BackupCipher` is an adapter boundary so a KMS or HSM can replace the local key.
|
|
|
|
The M16 review verified, and static tests now enforce, that a fresh nonce is drawn inside the chunk
|
|
loop rather than once per file, that each chunk is authenticated with associated data binding it to
|
|
its key id and index, and that a decryption failure removes both the partial file and the
|
|
destination so no readable plaintext survives a wrong key.
|