Skip to content

Commit 4d2b4fd

Browse files
author
Oracles Technologies LLC
committed
ci(security): supply-chain hardening — SHA-pin Actions, least-privilege perms, Dependabot
Cordyceps-class workflow hardening following the malicious-PR / supply-chain disclosure. The high-risk vectors were already absent (no pull_request_target, no untrusted input in shell, no PR->publish artifact flow); this closes the rest: - Pin every third-party Action to an immutable commit SHA (with version comments). - Least-privilege: top-level `permissions: contents: read`; only publish escalates (id-token: write for OIDC). audit.yml is pull_request-triggered, so it stays non-privileged by design. - Add .github/dependabot.yml (pip + github-actions) for dependency + Action-pin monitoring. - SECURITY.md: add "How We Secure Our Own Build Pipeline" — a verifiable, customer-facing statement of the posture (Trusted Publishing, attestations, SHA-pinned Actions, least-privilege, hash-pinned deps). Already in place: PyPI Trusted Publishing (OIDC), Sigstore attestations, hash-pinned requirements.lock + pip-audit --require-hashes --strict.
1 parent f0b29e6 commit 4d2b4fd

4 files changed

Lines changed: 61 additions & 18 deletions

File tree

.github/dependabot.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
version: 2
2+
3+
# Automated dependency + Action monitoring for the public Guardian SDK. Treat
4+
# high-severity alerts as blocking. The github-actions updater also bumps the
5+
# commit-SHA pins in the workflows so they stay current without losing
6+
# immutability.
7+
updates:
8+
- package-ecosystem: "github-actions"
9+
directory: "/"
10+
schedule:
11+
interval: "weekly"
12+
open-pull-requests-limit: 5
13+
labels: ["dependencies", "security"]
14+
15+
- package-ecosystem: "pip"
16+
directory: "/"
17+
schedule:
18+
interval: "weekly"
19+
open-pull-requests-limit: 5
20+
labels: ["dependencies", "security"]

.github/workflows/audit.yml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,22 @@ on:
1313
- cron: "0 9 * * 1" # every Monday at 09:00 UTC
1414
workflow_dispatch:
1515

16+
# Read-only — this workflow only audits + uploads a report artifact. Note it is
17+
# also pull_request-triggered, so it MUST stay non-privileged (no secrets, no
18+
# write perms, no consumption of its artifact by a privileged workflow).
19+
permissions:
20+
contents: read
21+
1622
jobs:
1723
audit:
1824
name: pip-audit
1925
runs-on: ubuntu-latest
2026

2127
steps:
22-
- uses: actions/checkout@v4
28+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
2329

2430
- name: Set up Python
25-
uses: actions/setup-python@v5
31+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
2632
with:
2733
python-version: "3.11"
2834

@@ -53,7 +59,7 @@ jobs:
5359

5460
- name: Upload audit report
5561
if: always()
56-
uses: actions/upload-artifact@v4
62+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
5763
with:
5864
name: audit-report
5965
path: audit-report.md

.github/workflows/publish.yml

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ on:
66
- "v*" # trigger on any version tag, e.g. v1.8.1
77
workflow_dispatch: # allow manual trigger from the Actions tab
88

9+
# Least-privilege default; only the publish job escalates (OIDC id-token).
10+
permissions:
11+
contents: read
12+
913
jobs:
1014
# ──────────────────────────────────────────────────────────────────────────
1115
# Build the source distribution and wheel in an isolated environment.
@@ -17,12 +21,12 @@ jobs:
1721
runs-on: ubuntu-latest
1822

1923
steps:
20-
- uses: actions/checkout@v4
24+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
2125
with:
2226
fetch-depth: 0 # full history so setuptools-scm / version attrs work
2327

2428
- name: Set up Python
25-
uses: actions/setup-python@v5
29+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
2630
with:
2731
python-version: "3.11"
2832

@@ -37,7 +41,7 @@ jobs:
3741
run: python -m build
3842

3943
- name: Upload dist artifacts
40-
uses: actions/upload-artifact@v4
44+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
4145
with:
4246
name: dist
4347
path: dist/
@@ -48,6 +52,9 @@ jobs:
4852
# stored as a secret. The id-token: write permission is required for GitHub
4953
# to mint the OIDC token that Sigstore uses to attest the release.
5054
#
55+
# Enable a required-reviewer protection rule on the `pypi` environment so a
56+
# human approves every publish even if CI is compromised.
57+
#
5158
# Sigstore attestations are generated automatically by pypa/gh-action-pypi-publish
5259
# when attestations: true is set. Users can verify with:
5360
# pip install pypi-attestations
@@ -67,12 +74,12 @@ jobs:
6774

6875
steps:
6976
- name: Download dist artifacts
70-
uses: actions/download-artifact@v4
77+
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
7178
with:
7279
name: dist
7380
path: dist/
7481

7582
- name: Publish to PyPI (with Sigstore attestations)
76-
uses: pypa/gh-action-pypi-publish@release/v1
83+
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1
7784
with:
7885
attestations: true

SECURITY.md

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -176,18 +176,28 @@ within 48 hours and coordinate a responsible disclosure timeline.
176176

177177
---
178178

179-
## Existing Supply Chain Protections
179+
## How We Secure Our Own Build Pipeline
180180

181-
| Protection | Status |
181+
Guardian is a security product, so compromising a Guardian release would compromise
182+
every downstream deployment. We therefore hold our own CI/CD to the standard we ask
183+
of customers. Every release is built by an immutable, least-privilege pipeline whose
184+
provenance you can verify independently (see *Verify Sigstore attestation*, above).
185+
186+
| Control | Status |
182187
|---|---|
183-
| PyPI package hashes published ||
184-
| Sigstore / SLSA attestations ||
185-
| Hash-pinned `requirements.lock` ||
186-
| Continuous `pip-audit` on wheel builds ||
187-
| Typosquatting stubs on PyPI | ✅ (4 variants monitored) |
188-
| SDK self-integrity check (`guardian verify`) | ✅ v2.6.0+ |
189-
| AI-mediated supply chain detection | ✅ API tier v2.6.0+ |
188+
| **PyPI Trusted Publishing (OIDC)** — no long-lived API tokens in CI ||
189+
| **Sigstore / SLSA build attestations** on every release ||
190+
| **Third-party Actions pinned to commit SHAs** (immutable; no mutable tags) ||
191+
| **Least-privilege workflows**`permissions: contents: read` default, scoped per job ||
192+
| **No `pull_request_target`**, no untrusted input in shell, no PR→publish artifact flow ||
193+
| **Hash-pinned `requirements.lock`** + `pip-audit --require-hashes --strict` (release-blocking) ||
194+
| **Dependabot** monitoring (`pip` + `github-actions`) ||
195+
| **PyPI package hashes published** + SDK self-integrity (`guardian verify`) ||
196+
| **Typosquatting stubs** on PyPI (4 variants monitored) ||
197+
| **AI-mediated supply chain detection** (`supplyChainDependencyInjection`, API tier) ||
190198

191199
---
192200

193-
*Last updated: 2026-05-18 — Guardian SDK v2.6.0*
201+
*Last updated: 2026-06-23 — supply-chain hardening pass (Trusted Publishing,
202+
SHA-pinned Actions, least-privilege workflows, Dependabot) following the Cordyceps
203+
malicious-PR disclosure.*

0 commit comments

Comments
 (0)