Watch
1
0
Fork
You've already forked RedFlag
0

supply-chain: gate our own deps, ship the verdict signed

dep-scan.sh gates go/npm/cargo on push and bakes an attested posture into the
release — embedded in the server, signed into the manifest. Reasoning and the
two Moby exceptions are in SECURITY.md.

(posture-builder runs rustup; bookworm's cargo is too old for cargo-audit.)
This commit is contained in:
Fimeg 2026-06-14 11:29:08 -04:00
commit e2dab2845a
18 changed files with 5563 additions and 27 deletions

View file

@ -469,6 +469,7 @@ func (h *DownloadHandler) buildReleaseManifest(version string) services.ReleaseM
GeneratedAt: time.Now().UTC().Unix(),
KeyID: h.signingService.GetCurrentKeyID(),
Components: services.ComponentCatalog(),
SupplyChain: services.EmbeddedPosture(),
}
platforms := []struct{ platform, arch string }{

View file

@ -0,0 +1,7 @@
{
"attested": false,
"generated_at": 0,
"substrate": {},
"scans": [],
"exceptions": []
}

View file

@ -0,0 +1,78 @@
package services
import (
_ "embed"
"encoding/json"
"log"
)
// Supply-chain posture — RedFlag's attestation of its OWN dependency hygiene,
// folded into the signed release manifest so it rides the same Ed25519 trust
// root as the binaries. The installer verifies the manifest signature before
// trusting any of this, so the posture cannot be forged by editing a file next
// to the binary: tamper breaks the signature and the install refuses.
//
// The content is generated at build time by scripts/dep-scan.sh (the same gate
// that fails the release on an un-accepted reachable vulnerability) and embedded
// here. A bare `go build` outside that pipeline embeds the committed stub with
// Attested=false — an honest "this build was not gated", which the installer
// treats as a refusal-worthy posture.
//go:embed posture-build.json
var postureBuildJSON []byte
// DependencyException is a known, accepted reachable vulnerability with the
// documented reason it is tolerated. Mirrors a line in .govulncheck-allow.
type DependencyException struct {
ID string `json:"id"`
Reason string `json:"reason"`
}
// EcosystemScan is one scanner's verdict over one ecosystem.
type EcosystemScan struct {
Ecosystem string `json:"ecosystem"` // go, npm, cargo
Tool string `json:"tool"` // govulncheck, npm-audit, cargo-audit
Status string `json:"status"` // clean, accepted, blocked
Accepted int `json:"accepted"`
Blocked int `json:"blocked"`
}
// SupplyChainPosture is RedFlag's own dependency attestation for this build.
type SupplyChainPosture struct {
// Attested is true only when this posture was produced by the gated build
// pipeline. A stub/dev build is Attested=false.
Attested bool `json:"attested"`
GeneratedAt int64 `json:"generated_at"`
Substrate map[string]string `json:"substrate"` // go, rustc, node, npm, docker versions
Scans []EcosystemScan `json:"scans"`
Exceptions []DependencyException `json:"exceptions"`
}
// Blocked reports whether any ecosystem carries an un-accepted reachable vuln.
// A clean build never embeds a blocked scan (the gate fails the release first),
// so this is defense-in-depth for a tampered or hand-built artifact.
func (p SupplyChainPosture) Blocked() bool {
for _, s := range p.Scans {
if s.Blocked > 0 {
return true
}
}
return false
}
// loadEmbeddedPosture parses the build-time posture. A parse failure is logged
// and degraded to an explicit un-attested posture rather than a crash — the
// manifest still signs, and the installer refuses on Attested=false.
func loadEmbeddedPosture() SupplyChainPosture {
var p SupplyChainPosture
if err := json.Unmarshal(postureBuildJSON, &p); err != nil {
log.Printf("[ERROR] [server] [posture] embedded posture-build.json unparseable: %v", err)
return SupplyChainPosture{Attested: false}
}
return p
}
// EmbeddedPosture returns the build-time supply-chain posture for this server.
func EmbeddedPosture() SupplyChainPosture {
return loadEmbeddedPosture()
}

View file

@ -40,12 +40,18 @@ type ManifestComponent struct {
}
// ReleaseManifest is the signed component+artifact catalog for one version.
//
// SupplyChain is the running server's own dependency attestation (built-time
// scan verdict + accepted exceptions + build substrate). It rides the manifest
// signature so the installer can show "safe checks" without a second trust root,
// and refuse on an un-attested or blocked posture.
type ReleaseManifest struct {
Version string `json:"version"`
GeneratedAt int64 `json:"generated_at"`
KeyID string `json:"key_id"`
Components []ManifestComponent `json:"components"`
Artifacts []ManifestArtifact `json:"artifacts"`
SupplyChain SupplyChainPosture `json:"supply_chain"`
}
// ComponentCatalog returns the fixed component set for a release.

View file

@ -488,6 +488,48 @@ if [ $MANIFEST_RC -ne 0 ]; then
fi
echo "✓ Manifest signature verified (key ${MANIFEST_KEY_ID})"
# --- Supply-chain posture: RedFlag's own dependency attestation ---------------
# The server folds its build-time scan verdict + accepted exceptions into the
# signed manifest. We just verified that signature, so this posture cannot be
# forged by editing a file next to the binary. Surface it, and refuse on a
# BLOCKED posture (a build carrying an un-accepted vulnerability) — the same
# fail-closed stance the binary takes at runtime. An UN-ATTESTED posture (a
# server built from source outside RedFlag's release pipeline) is a loud warning,
# not a refusal: the operator built it and owns that trust.
POSTURE_CHECK=$(mktemp)
cat <<'POSTURE_EOF' > "$POSTURE_CHECK"
import sys, json
p = (json.load(open(sys.argv[1])).get("supply_chain") or {})
scans = p.get("scans", [])
sub = p.get("substrate", {})
blocked = [s for s in scans if s.get("blocked", 0) > 0]
print("Supply-chain posture:")
if sub:
print(" built on: " + ", ".join("%s %s" % (k, v) for k, v in sub.items() if v))
for s in scans:
print(" %-6s %-12s %s (accepted=%d blocked=%d)" % (
s.get("ecosystem", ""), s.get("tool", ""), s.get("status", ""),
s.get("accepted", 0), s.get("blocked", 0)))
for e in p.get("exceptions", []):
print(" accepted exception: %s" % e.get("id", ""))
if blocked:
sys.exit(4)
if not p.get("attested"):
sys.exit(3)
POSTURE_EOF
python3 "$POSTURE_CHECK" "$TMP_MANIFEST"
POSTURE_RC=$?
rm -f "$POSTURE_CHECK"
if [ "$POSTURE_RC" = "4" ]; then
rm -f "$TMP_MANIFEST"
echo "ERROR: Server build carries un-accepted dependency vulnerabilities — refusing to install." >&2
exit 1
elif [ "$POSTURE_RC" = "3" ]; then
echo "WARNING: This server was built outside RedFlag's gated release pipeline — its dependency posture is not independently attested." >&2
elif [ "$POSTURE_RC" = "0" ]; then
echo "✓ Supply-chain posture attested"
fi
# Resolve components → artifacts for this platform/arch.
# The Python helper reads the manifest, pairs each component with its matching
# artifact entry, and emits tab-separated lines: