Publish curated ITWorx-ModelForge source from 990401737f96c8d46a9f7b0d84994516cadcb6a0
This commit is contained in:
1 parent
7082ab955a
commit
848f0d8f8a
434 files changed
+67298
-4315
No files matched your search
@@ -5,8 +5,11 @@ returns an `mfsvc_…` bearer secret once. Only SHA-256, a non-secret prefix, ti
|
||||
status, rate/concurrency policy and explicit capability scopes are stored. Secrets are absent from
|
||||
audit events and ordinary logs.
|
||||
|
||||
Human credentials use the separate `mfop_` namespace and deployment-owned identity registry described
|
||||
in [Operator access](../operations/OPERATOR_ACCESS.md). They never authenticate capability or node
|
||||
requests; machine credentials never become human principals or Console sessions.
|
||||
|
||||
Gateway authentication rejects missing, malformed, unknown, revoked or expired credentials,
|
||||
disabled clients and missing capability scope. Each client has a sliding one-minute request limit
|
||||
and a concurrent-request limit. Credential creation and revocation are audited. The frontend keeps
|
||||
the one-time value in memory only and never reloads plaintext.
|
||||
|
||||
@@ -1,102 +1,301 @@
|
||||
# Supply-chain release gate
|
||||
|
||||
## What is inventoried
|
||||
## Release boundary
|
||||
|
||||
RC-DEL-02 is a managed, fail-closed evidence gate. Pull requests, default runs, explicit `full`
|
||||
runs and explicit `security` runs all require the complete source phase. Only an explicit
|
||||
`workflow_dispatch` with `profile=security` builds and scans the four shipping images; `full` does
|
||||
not silently turn into the expensive image profile. Neither profile can pass because a required
|
||||
scanner was absent, timed out, lost network access, returned malformed data or silently omitted a
|
||||
profile-required component. The job does not deploy or contact a ModelForge production service.
|
||||
|
||||
The checkout uses the commit selected by the Gitea run, `fetch-depth: 0`, tag fetching and
|
||||
`persist-credentials: false`. An explicit fetch uses full head and tag refspecs.
|
||||
`scripts/security_gate.py history` separately proves that Git reports the checkout as non-shallow,
|
||||
that `HEAD` equals `GITHUB_SHA`, and that every advertised remote head and tag has the same local
|
||||
object. It also records the canonical origin, all-ref and reachable-commit counts, root commits and
|
||||
object-reachability proof. A non-shallow single-branch clone is incomplete and fails; checkout
|
||||
configuration alone is not proof.
|
||||
|
||||
The job has a 120-minute bound. The explicit worst-case step bounds for the image profile total 108
|
||||
minutes, including five minutes each for the always-run evaluator and artifact upload, leaving a
|
||||
12-minute job-level reserve. A bootstrap `security-gate-result.json` starts in the fail state before
|
||||
checkout. Evidence and tools each use an exclusive `mktemp` directory whose prefix includes run ID,
|
||||
run attempt and SHA and whose unpredictable suffix prevents stale-path reuse. Both roots must be
|
||||
real, runner-owned mode-0700 directories outside the workspace. The evidence location survives
|
||||
checkout cleaning and remains uploadable when checkout fails. The evaluator rejects evidence-root,
|
||||
directory or file symlinks/reparse points, non-regular files, ownership/mode surprises and paths
|
||||
outside the evidence root. On Linux it walks with descriptor-relative `openat`/`O_NOFOLLOW`; on
|
||||
Windows it rejects reparse points and compares pre-open, open-handle and post-read identities. It
|
||||
validates owner/mode/type on opened handles, reads and hashes the same descriptors, retains one byte
|
||||
snapshot, then revalidates ancestry, identity, size and timestamps before every consumption. A
|
||||
same-inode mutation or lstat/open swap therefore fails without consuming replacement content.
|
||||
Missing status records, timeout exits and interrupted or failed downloads/builds/scans all remain
|
||||
blocking evidence.
|
||||
|
||||
## Pinned scanner acquisition
|
||||
|
||||
No floating scanner action, `latest` download or `curl | sh` installer is used. The runner downloads
|
||||
and verifies these exact artifacts before execution:
|
||||
|
||||
| Scanner | Exact version/artifact | SHA-256 |
|
||||
| --- | --- | --- |
|
||||
| pip-audit | `pip_audit-2.10.1-py3-none-any.whl` | `99ef3f600a317c1945f1e89e227ef26e1c2d618429b8bd3fa6f4f7c440c4611a` |
|
||||
| Gitleaks | `gitleaks_8.30.1_linux_x64.tar.gz` | `551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb` |
|
||||
| Trivy | `trivy_0.74.0_Linux-64bit.tar.gz` | `2ae6fe3ee734b7fdf11335663e18c75ea12dccc76062f09f164a3b0f8be4371a` |
|
||||
|
||||
The pip-audit wheel and every dependency in its scanner environment are fully resolved in
|
||||
`config/security/pip-audit-toolchain.lock`. Each official PyPI wheel has an exact version and
|
||||
SHA-256; acquisition uses `--require-hashes --only-binary=:all:` from `https://pypi.org/simple`, and
|
||||
installation is offline from those verified downloads with `--no-index --require-hashes`. The
|
||||
evaluator verifies reported runtime versions again. Trivy version/DB evidence is collected in the
|
||||
mandatory source phase, so `full` does not depend on image execution.
|
||||
|
||||
Both Trivy DB `UpdatedAt` and `DownloadedAt` must be well-formed explicit UTC timestamps, must not
|
||||
be future-dated, and `DownloadedAt` cannot predate `UpdatedAt`. The DB update may be at most 48
|
||||
hours old and the local download at most 24 hours old at evaluation time. Missing, null, malformed,
|
||||
non-UTC, future or stale metadata fails. npm is runner-supplied and its exact observed version is
|
||||
retained rather than claimed in advance.
|
||||
|
||||
## Required scan matrix
|
||||
|
||||
The source phase collects findings without ignore flags or finding-based early exit:
|
||||
|
||||
| Target | Evidence |
|
||||
| --- | --- |
|
||||
| Complete Git history | Gitleaks `git`, `--log-opts=--all`, `--redact=100` |
|
||||
| API source | pip-audit of every `[project].dependencies`, optional group and locked build tool |
|
||||
| Node Agent source | same, including its exact local API dependency |
|
||||
| Runtime Worker source | same, including both `dev` and `inference` groups |
|
||||
| Console lock | `npm audit --json` for all dependencies |
|
||||
| Console production lock | `npm audit --omit=dev --json` |
|
||||
| Repository filesystem | Trivy `vuln,misconfig`, retaining UNKNOWN through CRITICAL |
|
||||
|
||||
Python declarations are read with `tomllib`; a hand-maintained requirement subset is not accepted.
|
||||
Each PEP 517 backend requirement must have one exact version that matches both gate policy and
|
||||
`config/security/build-toolchain.lock`. The lock pins `pip==26.2.1`, `setuptools==84.0.0`,
|
||||
`wheel==0.48.0`, `msgpack==1.2.2`, `hatchling==1.32.4`, `editables==0.5` and Hatchling's complete build-time
|
||||
dependency closure. Every artifact is installed from PyPI with `--require-hashes --only-binary=:all:`.
|
||||
The msgpack hashes cover CPython 3.11-3.14 glibc x86_64/aarch64 wheels and
|
||||
CPython 3.12-3.14 Alpine musl x86_64/aarch64 wheels used by managed validation and release images.
|
||||
The managed source-validation environment and all Python image builds install this lock before
|
||||
packaging projects, then use `--no-build-isolation` so pip cannot resolve a second build
|
||||
environment. The gate audits every locked tool as an exact direct package and compares the PEP 517
|
||||
requirements against the lock.
|
||||
The three first-party distributions are injected into their source inventories, and local
|
||||
cross-component pins must resolve to the matching project version. Source reports therefore show
|
||||
explicit coverage of `modelforge-api`, `modelforge-node-agent` and
|
||||
`modelforge-runtime-worker`, even though a public advisory service may correctly report that a
|
||||
private distribution is not present in its index. Those exact locally proven names are separated
|
||||
before the public advisory lookup because `pip-audit --strict` correctly rejects a distribution
|
||||
that PyPI cannot identify; every other declared direct package remains in the strict audit input.
|
||||
Source pip-audit is intentionally allowed to resolve dependencies: every declared direct package
|
||||
must appear at its exact manifest version, while every additional resolved transitive package and
|
||||
version is retained in `resolved_transitive_packages`. Missing or version-drifted direct packages
|
||||
fail. The resolved source report can omit the build-environment tools `packaging`, `pip`,
|
||||
`setuptools` and `wheel`. The gate accepts those omissions only for source resolution: all four
|
||||
remain hash-locked, and shipping-image inventory still requires `packaging` when present while
|
||||
forbidding retained installers. Trivy scans the repository and immutable images. No other runtime
|
||||
package receives this exception. The retained pip inputs
|
||||
must preserve the original PEP 508 declarations, including extras such as `uvicorn[standard]` and
|
||||
`psycopg[binary]`, plus every locked build-tool entry; reconstructing a bare `name==version` line
|
||||
for an extra or omitting a lock entry fails. Duplicate project declarations are rejected before
|
||||
aggregation and name the exact manifest, TOML source and line for both declarations. Identical as
|
||||
well as conflicting duplicate pip result rows are malformed. Source audits never use `--no-deps`
|
||||
or `--disable-pip`.
|
||||
|
||||
The shipping-image phase builds exactly four non-pushed local tags:
|
||||
|
||||
```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
|
||||
modelforge-security-api:${GITHUB_SHA}
|
||||
modelforge-security-web:${GITHUB_SHA}
|
||||
modelforge-security-node-agent:${GITHUB_SHA}
|
||||
modelforge-security-runtime-worker:${GITHUB_SHA}
|
||||
```
|
||||
|
||||
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.
|
||||
Every build uses `--pull`, an exact SHA tag and exact `org.opencontainers.image.source`, `revision`
|
||||
and release `version` labels. Sanitized Docker identity evidence records those labels, the unique
|
||||
tag and immutable `sha256:` image ID for all four images. The retained set must contain exactly four
|
||||
release identities, exactly one approved SHA tag per image, no mutable or attacker aliases, no
|
||||
unexpected digest aliases and no duplicate image IDs. Trivy scans the image ID, not a mutable tag;
|
||||
a gate-owned normalizer preserves Trivy's component target separately while binding every retained
|
||||
`Results[].Target` and its own `ScannedImageID` to that exact ID. The evaluator cross-checks
|
||||
`ArtifactType`, `ArtifactName`, every nested target, `Metadata.ImageID`, repo tags/digests and the
|
||||
reference against Docker identity evidence; Trivy's distinct config `ArtifactID` must remain an
|
||||
immutable SHA-256 value.
|
||||
|
||||
## SBOM
|
||||
All four Dockerfiles declare the canonical HTTPS source URL
|
||||
`https://git.example.com/example/modelforge`. The workflow validates both repository identity
|
||||
and server URL before building, and the evaluator requires the same canonical URL in each inspected
|
||||
image. Python images take a repository-root build context so they can consume the shared hash lock.
|
||||
The curated public-source exporter intentionally rewrites that private static label to its existing
|
||||
sanitized URL and records it as `sourceRepository` in `PUBLIC_SOURCE_MANIFEST.json`. The manifest
|
||||
hashes all four transformed Dockerfiles; both the release builder and source gate require their
|
||||
labels to match that manifest identity. Public release provenance records the same sanitized source
|
||||
URL alongside the canonical source SHA and manifest digest. Private managed image builds stamp and
|
||||
evaluate the exact trusted repository URL resolved by their workflow.
|
||||
|
||||
`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.
|
||||
Python executes inside the exact API, Node Agent and Runtime Worker image IDs to enumerate installed
|
||||
distributions through `importlib.metadata`. Each required first-party distribution must equal the
|
||||
corresponding source project version. Those complete image inventories are audited exactly with
|
||||
`pip-audit --strict --no-deps --disable-pip`; unexpected or missing image packages fail. The final
|
||||
Runtime Worker inventory must not contain pip, setuptools, wheel or editables; retaining any
|
||||
installer/build backend blocks the gate. The image audit permits only the established
|
||||
`setuptools`/`wheel` report omissions, while its exact image inventory and Trivy evidence remain
|
||||
mandatory.
|
||||
The digest-pinned PyTorch base also exposes exact Conda-management distributions and CUDA-local
|
||||
PyTorch wheels that do not exist at those versions on PyPI. Only the enumerated Runtime Worker
|
||||
target/name/version tuples are excluded from the PyPI requirements input; every tuple remains
|
||||
mandatory in the retained image inventory and the whole immutable image remains subject to Trivy.
|
||||
The allowlist is `conda==25.5.0`, `conda-build==25.5.0`,
|
||||
`conda-libmamba-solver==25.3.0`, `libmambapy==2.1.1`, `menuinst==2.2.0`,
|
||||
`torch==2.7.1+cu128`, `torchaudio==2.7.1+cu128`, and `torchvision==0.22.1+cu128`.
|
||||
Absence or version drift fails before vulnerability evaluation.
|
||||
All four release images are rebuilt with `--pull --no-cache` for the explicit security profile.
|
||||
This prevents a previously cached package-manager upgrade layer from masquerading as a current
|
||||
base-OS scan. The release builder uses the same cache-free policy. Runtime images also remove
|
||||
installers after their final dependency check.
|
||||
The Web build sets `VITE_API_BASE_URL=https://security-scan.invalid`; exact image
|
||||
identity records its canonical nginx bundle path. Evidence collection copies that actual path from
|
||||
the exact immutable image ID, starts that image, and queries the canonical served build-provenance
|
||||
asset generated by Vite. The served bytes must equal the hashed bundle asset. That exact-schema
|
||||
asset binds the intentional API origin with SHA-256; the compiled runtime chunk must reference the
|
||||
same origin and binding, and both digests are retained inside the whole-bundle digest. Every
|
||||
JavaScript chunk, source map, HTML and CSS asset is scanned for direct or
|
||||
split/concatenated/template HTTP(S) and WebSocket origins. The pinned React build's W3 namespace
|
||||
constants and React error-document origin are separately recorded as exact non-API origins and are
|
||||
valid only in their expected namespace/error contexts. Missing provenance, any other origin,
|
||||
additional attacker coexistence, dead expected markers, IPv4/IPv6 loopback and localhost-family
|
||||
origins all fail. The Web image is static nginx and has no invented Python inventory. Ordinary
|
||||
validation uses three separate virtual environments so components cannot inherit one another's
|
||||
resolution.
|
||||
|
||||
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.
|
||||
## Secret evidence and historical false positives
|
||||
|
||||
## Image provenance
|
||||
Gitleaks 8.30.1 has upstream false-negative regression #2170 and also drops newly added rules when
|
||||
they are combined with `extend.useDefault`. The gate therefore scans the same complete history
|
||||
twice: built-in default rules run on a temporary config-free worktree, then `.gitleaks.toml` runs
|
||||
narrow no-entropy rules for GitHub's documented `ghp_`, `gho_`, `ghu_`, `ghr_`, `github_pat_` and
|
||||
both classic and 2026 stateless `ghs_` token formats. The temporary raw reports are merged outside
|
||||
retained evidence. Neither configuration path contains a path, commit, or global allowlist.
|
||||
|
||||
`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.
|
||||
Before the repository scan, the exact pinned Linux binary must independently find canonical
|
||||
synthetic `ghp_`, `gho_`, `ghu_`, `ghs_`, `ghr_` and `github_pat_` histories through the applicable
|
||||
custom rules, plus a canonical AWS key through a built-in/default rule. Both configurations must
|
||||
leave a benign control clean. Trigger values are assembled only at test runtime. Traps remove the
|
||||
temporary histories and raw reports; scanner, assertion, worktree/prune or `rm` cleanup failure
|
||||
blocks the gate.
|
||||
|
||||
## CVE triage
|
||||
The repository's raw Gitleaks JSON is temporary and is never uploaded. Before retention, the
|
||||
normalizer emits an exact version-1 object and exact finding keys: a constrained rule ID and a
|
||||
SHA-256 of Gitleaks' exact fingerprint. Unknown keys and `Secret`, `Match`, `Line`, author, email,
|
||||
the un-hashed fingerprint, source path or commit metadata fail or are absent from the artifact.
|
||||
|
||||
Findings are classified, and the classification is evidence rather than a label:
|
||||
A real pinned Gitleaks 8.30.1 scan on 2026-08-30 proved that the full non-shallow history contains
|
||||
two findings for one old M15 AES unit-test fixture. Rewriting canonical history is not an acceptable
|
||||
release operation. The fixture bytes were synthetic test material and never a credential or a
|
||||
production backup key, so the two observations are classified `FALSE_POSITIVE` in
|
||||
`config/security/gitleaks-false-positives.json` by exact SHA-256 of the Gitleaks fingerprint and
|
||||
exact rule ID. The registry contains no path, glob, commit or secret value.
|
||||
|
||||
```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
|
||||
This is deliberately narrower than a scanner allowlist:
|
||||
|
||||
- each classification must match exactly one sanitized finding;
|
||||
- a missing, changed, duplicated or additional fingerprint fails;
|
||||
- duplicate registry entries and wildcard rule IDs fail;
|
||||
- owner, classification date, review date, tracking reference and reason are mandatory;
|
||||
- the review window is at most 30 days and a stale review fails;
|
||||
- no Gitleaks finding can use the vulnerability accepted-risk mechanism.
|
||||
|
||||
Any new leak therefore remains blocking, including another finding from the same rule or file.
|
||||
|
||||
## Central decision and accepted risk
|
||||
|
||||
Scanners collect facts; only `scripts/security_gate.py evaluate` decides. It rejects missing, empty
|
||||
or malformed reports, unexpected scanner exits, network/tool failures, timeouts, shallow history,
|
||||
version drift, source/inventory mismatches and every expected-versus-actual target gap. It then
|
||||
blocks:
|
||||
|
||||
- every unclassified Gitleaks finding;
|
||||
- every pip-audit vulnerability;
|
||||
- every npm vulnerability;
|
||||
- every Trivy UNKNOWN, HIGH or CRITICAL vulnerability or misconfiguration.
|
||||
|
||||
LOW and MEDIUM Trivy observations remain in the raw machine-readable reports but are not RC-DEL-02
|
||||
blocking severities. No scanner is instructed to omit them.
|
||||
|
||||
`config/security/accepted-risks.json` is a versioned object. It starts empty and contains only
|
||||
separately reviewed, temporary exceptions. An entry has exactly these fields:
|
||||
|
||||
```json
|
||||
{
|
||||
"scanner": "trivy",
|
||||
"target": "node-agent",
|
||||
"advisory_id": "CVE-YYYY-NNNN",
|
||||
"package": "package-name",
|
||||
"installed_version": "1.2.3",
|
||||
"classification": "ACCEPTED_TEMPORARY",
|
||||
"owner": "named owner",
|
||||
"accepted_on": "2026-08-30",
|
||||
"expires_on": "2026-09-29",
|
||||
"tracking": "https://issue.example/RC-DEL-02",
|
||||
"reason": "Evidence-backed reason that the no-fix risk is temporarily acceptable."
|
||||
}
|
||||
```
|
||||
|
||||
"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.
|
||||
The identity is the exact five-value tuple `(scanner, target, advisory_id, package,
|
||||
installed_version)`. Classification must be exactly `ACCEPTED_TEMPORARY`; wildcards and extra
|
||||
fields fail. Only pip-audit, npm-audit and Trivy
|
||||
vulnerabilities with no available fix are eligible. The window is at most 30 calendar dates;
|
||||
`expires_on` is the first invalid date. Future,
|
||||
expired, duplicate, ambiguous, unused/stale, malformed and fix-available entries all fail the gate.
|
||||
An accepted entry that no longer matches a finding is not harmless configuration; it is stale
|
||||
policy and blocks the release.
|
||||
|
||||
## Dependency pinning
|
||||
For the v1.2.2 candidate, the registry contains 23 exact Trivy tuples for the Node Agent Debian
|
||||
Trixie base. Every tuple was reported without an upstream fix by the exact-image run, is owned by
|
||||
the ModelForge security owner, expires on 2026-10-02, and is tracked in
|
||||
`https://git.example.com/example/modelforge/issues/24`. The current Bookworm alternative had
|
||||
additional critical no-fix findings; the tested distroless Debian 13 alternative changes Python,
|
||||
UID and runtime compatibility. No fix-available finding is accepted. The next scan must consume
|
||||
every tuple exactly; a fixed, changed, missing or newly introduced tuple fails closed.
|
||||
The source-only `full` profile validates the registry schema and active dates but defers the
|
||||
unused/stale decision for image-targeted Trivy entries, because it produces no image evidence.
|
||||
Only the explicit `security` profile may consume those entries, and it fails when any such tuple is
|
||||
unused. A passing `full` run therefore never substitutes for the mandatory exact-image run.
|
||||
|
||||
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.
|
||||
Trivy report parsing is strict before risk matching. JSON evidence rejects duplicate object keys;
|
||||
the pinned Trivy 0.74.0 filesystem scan must report `ArtifactType=repository`, and its
|
||||
`ArtifactName` must equal the resolved checkout path exactly. Every result target must remain
|
||||
inside that checkout. Image `ArtifactType`, `ArtifactName` and every result target are likewise
|
||||
required and bound to the immutable image identity. Each vulnerability must contain
|
||||
non-empty `VulnerabilityID`, `PkgName`, `InstalledVersion` and a valid `Severity`; malformed values
|
||||
become schema-policy failures, never fallback identities, and therefore can never be waived.
|
||||
|
||||
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.
|
||||
## Retained result
|
||||
|
||||
## Container posture
|
||||
The evaluator always replaces the bootstrap result when it runs and writes:
|
||||
|
||||
```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
|
||||
```
|
||||
- commit, ref, run ID, profile and evaluation time;
|
||||
- exact scanner versions and Trivy database time;
|
||||
- every expected operation's exit code;
|
||||
- SHA-256 for every retained, manifest-approved report (unknown evidence paths fail before bytes are
|
||||
read or hashed);
|
||||
- expected and actual targets by scan class;
|
||||
- resolved source transitive packages and sanitized image identities when applicable;
|
||||
- blocked findings;
|
||||
- applied and unused/invalid false-positive classifications;
|
||||
- applied and unused/invalid accepted risks;
|
||||
- the final `PASS` or `FAIL` decision.
|
||||
|
||||
## Network boundaries
|
||||
The security job uploads its unique retained
|
||||
`/tmp/modelforge-security-evidence-${run_id}-${run_attempt}-${sha}.${random}` directory with
|
||||
`gitea-upload-artifact@81f940d004763f986ba3582c007fd842dd5cb0d7`, `if: always()`,
|
||||
`if-no-files-found: error` and 90-day retention. Its artifact name includes run ID, run attempt and
|
||||
source SHA. Upload failure is a failed gate, not an evidence-retention warning.
|
||||
|
||||
```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.
|
||||
Implementation and synthetic local tests are not release verification. RC-DEL-02 remains
|
||||
unverified until both an exact-head managed `security` run (including its retained artifact) and an
|
||||
exact-head managed `full` run succeed. A first real runner execution may legitimately be red because
|
||||
it sees current dependency/image advisories or runner/network deficiencies; no historical
|
||||
vulnerability risk has been seeded to manufacture a green result.
|
||||
Reference in new issue
Block a user