This document satisfies OpenSSF Baseline OSPS-DO-06.01 — "the project documentation MUST include a description of how the project selects, obtains, and tracks its dependencies."
- Standard library + first-party SDKs first. Reach for a third-party dep only when the stdlib or an official cloud SDK can't do the job.
- Reachability over reputation. A dep with a published CVE is fine
if
govulncheckproves the vulnerable function is never called from our code. Conversely, a dep without CVEs is not automatically safe — we still SHA-pin and scan it. - Pin everything that crosses a trust boundary. Hashes for pip,
go.sumfor Go modules, SHA digests for Docker base images, commit SHAs for GitHub Actions. - No suppressions.
.trivyignore,# noqa,// nolint:, and# nosemgrepare reserved for documented false positives with a reason link in the PR description — not for silencing inconvenient findings.
| Ecosystem | Where deps live | Pinning mechanism | Vuln scanning |
|---|---|---|---|
| Go | go.mod + go.sum |
go.sum hashes every direct + transitive dep |
govulncheck (reachability-aware), osv-scanner (via Scorecard), trivy fs |
| Python (docs) | docs/requirements.in (sources) + docs/requirements.txt (compiled with --generate-hashes) |
pip install --require-hashes in push.yaml docs-build step |
pip-audit, Scorecard pinned-deps check |
| npm (docs examples) | example package*.json files |
Lockfile-aware install (npm ci when lockfile present, falls back to npm install) |
Scorecard pinned-deps check |
| Docker base images | Dockerfiles + .Dockerfiles at repo root + example dirs |
SHA digest pin: python@sha256:401f..., node:22-alpine@sha256:757e... |
trivy image per published image |
| GitHub Actions | .github/workflows/* + .github/actions/* |
Commit SHA pin with # vX.Y.Z comment for human-readability |
Scorecard pinned-deps check; Semgrep custom rules |
| End-user installer tools | sc.sh (Pulumi installer) |
Tarball + SHA256 checksum verification before extract | n/a (sc.sh is shipped, not built against) |
When a new dep is considered:
- stdlib check — does Go / Python stdlib already cover it? If yes, reject the dep.
- First-party SDK check — for cloud-related work, prefer the official AWS/GCP/Azure/Kubernetes SDK over community wrappers.
- Maintenance & community signal — last release within 12 months, open-issue:open-PR ratio not absurd, no orphaned-maintainer signals in the dep's repo.
- License compatibility — MIT, Apache-2.0, BSD-3-Clause OK. GPL / AGPL flagged for maintainer review (not auto-rejected, but the downstream-license implications need to be understood).
- CVE history —
govulncheck+osv.devlookup on the candidate. Active CVEs aren't a blocker if reachability proves we don't hit them, but they're declared in the PR. - Transitive blast radius — how many additional modules does this pull in? A 1-line utility with 47 transitive deps is rejected.
| Ecosystem | How we obtain | How we verify |
|---|---|---|
| Go modules | go.sum hash check on go mod download |
Module proxy + SHA256 |
| Python (pip) | pip install --require-hashes -r requirements.txt — fails if any package missing hash |
sha256/sha384 in requirements.txt |
| Docker base images | docker pull <image>@sha256:<digest> |
Manifest digest |
| GitHub Actions | uses: owner/repo@<commit-sha> # vX.Y.Z |
Commit SHA |
Pulumi (end-user sc.sh) |
curl https://github.com/pulumi/pulumi/releases/download/v${VER}/pulumi-v${VER}-linux-x64.tar.gz + checksum from same release |
sha256sum -c against checksums.txt from same GitHub Release |
- Security advisories: merged within 24 hours via Dependabot security alerts. Branch protection requires CI green + signed commits + review before merge — same gates as any other change.
- Routine version bumps: Dependabot opens grouped PRs weekly per
ecosystem (
gomod-minor-and-patch,actions-minor-and-patch,docker-minor-and-patch,pip-minor-and-patch). The grouping config also bundles allsimple-container-com/actions/*bumps into a single PR per actions-repo release. - Major version bumps: opened manually (or by Dependabot when the group splits them), reviewed with codex + gemini, validated via branch-preview build before merge.
- Base image rebuilds: every published image is rebuilt on every prod release (no float on the digest — the digest itself moves when the underlying Dockerfile is touched).
The PR pipeline runs:
go vet,staticcheck,golangci-lint- Semgrep (custom org-policy rules — see
simple-container-com/actions) - CodeQL (Go), triggered on all-branch push for full SAST coverage
govulncheck(reachability-aware)- Trivy image scan on each published Docker artifact
- TruffleHog secret scan on the diff
- Scorecard runs daily; the badge surfaces the current score
A subset of findings is intentionally accepted as documented false
positives — those live in PR descriptions and as OpenVEX
not_affected statements in vex/openvex.json,
never in a scanner-suppression file (no .trivyignore, no
# nosemgrep, no // nolint: for vuln findings).
Some surfaces consume our artifacts directly:
dist.simple-container.com/sc-*.tar.gz— thescCLI tarballsdist.simple-container.com/sc.sh— the bootstrap installersimplecontainer/*Docker Hub images
These are not "deps" in the tree-sense, but the trust chain matters the same way: each ships with cosign keyless signature + SLSA L3 provenance + SHA256. Consumers verify via the patterns documented in SECURITY.md.
This section satisfies OpenSSF Baseline OSPS-VM-05.01 ("the project documentation MUST include a policy that defines a threshold for remediation of SCA findings related to vulnerabilities and licenses") and is the contract the pre-release gate enforces.
| Severity (CVSS or distro rating) | Remediation SLA | Behaviour while open |
|---|---|---|
| CRITICAL (9.0–10.0) | within 24 hours of confirmation if there is a known patched version; within 7 days if upstream fix pending | merge gate blocks all non-emergency PRs |
| HIGH (7.0–8.9) | within 30 days | merge gate blocks; documented exceptions require VEX (see below) |
| MEDIUM (4.0–6.9) | within 90 days | best-effort with batch bumps via Dependabot |
| LOW (0.1–3.9) | next scheduled SCA pass (~weekly) | informational |
The SLA clock starts when the advisory is confirmed reachable in our
codebase by govulncheck (reachability-aware). If govulncheck reports
an advisory as not reachable, the finding does NOT block — it is
declared not_affected via vex/openvex.json with the govulncheck
output as evidence (per OSPS-VM-04.02).
| License class | Allowed for direct + transitive deps |
|---|---|
| MIT / Apache-2.0 / BSD-3-Clause / BSD-2-Clause / ISC / MPL-2.0 | ✅ Allowed |
| LGPL (any version) | |
| GPL / AGPL (any version) | ❌ Rejected for runtime; allowed for build-only tooling iff the tool is not redistributed |
| Unknown / proprietary | ❌ Rejected |
License compliance is checked at PR review time. License-incompatible deps are blocked at merge by maintainer review (no automated license scanner yet — tracked internally).
This section satisfies OpenSSF Baseline OSPS-VM-05.02 ("a policy to address SCA violations prior to any release") and OSPS-VM-05.03 ("automatically evaluated against a documented policy ... then blocked in the event of violations").
Production releases are cut automatically on every merge to main
(.github/workflows/push.yaml), so "pre-release" semantically equals
"pre-merge" — every release IS a merge to main. The SCA gate fires
on every PR + every push to main:
| Tool | Coverage | Blocks on |
|---|---|---|
govulncheck (.github/workflows/govulncheck.yml) |
Reachable Go vulnerabilities (call-graph aware) | Any reachable advisory finding |
CodeQL (.github/workflows/codeql.yml) |
Common-vulnerability patterns (SQL injection, XSS, command injection, taint flow, SSRF) | ERROR-severity findings |
Semgrep (.github/workflows/semgrep.yml) |
Custom rules from the SC org (sigstore, gha-extras, pulumi-iac, go-canon) | ERROR-severity findings |
TruffleHog (.github/workflows/security-scan.yml) |
Leaked secrets in diff | Any verified secret |
| Dependabot | Daily advisory poll → auto-PR for known vulns | Open security alert (visibility, not blocking, but the auto-PR becomes a blocking item per the SLA above) |
| Trivy / Grype (in shared security-scan workflow) | SBOM-based scan of source-tree deps | Currently reports counts only; planned to graduate to blocking on HIGH/CRITICAL once VEX consumption is wired in |
The only sanctioned suppression channel is VEX: edit
vex/openvex.json and add a not_affected statement with:
vulnerability.@id— the OSV / CVE / GHSA IDjustification— one of the OpenVEX standard values (vulnerable_code_not_in_execute_path,inline_mitigations_already_exist, etc.)impact_statement— free-form explanation citing evidence (e.g., govulncheck output, code-path analysis, mitigation in place)
.trivyignore, # nosemgrep, // nolint:, # noqa are NOT
sanctioned suppression channels. Any of these in a PR must point at
a documented false positive in the PR description; the project
prefers the formal VEX statement for tracking.
Per OSPS-VM-05.03, the suppression flow is also "documented policy":
findings reachable in our code WITHOUT a VEX not_affected entry
block the merge. Findings declared not_affected in VEX bypass the
gate.