Skip to content

Commit 304cbd8

Browse files
authored
Merge pull request #136 from kellenmurphy/ci/scope-dependency-scanning
ci: scope whole-tree CVE scanning off the pull-request path
2 parents 630dbfa + 489c11f commit 304cbd8

2 files changed

Lines changed: 51 additions & 38 deletions

File tree

.github/workflows/ci.yml

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@ on:
66
pull_request:
77
branches: [main]
88
types: [opened, synchronize, reopened, labeled]
9+
schedule:
10+
# Weekly whole-tree vulnerability sweep (Mondays 06:00 UTC), independent of
11+
# PR/push activity, so a newly-published advisory surfaces within a week even
12+
# when the repo is quiet.
13+
- cron: '0 6 * * 1'
914
workflow_dispatch:
1015

1116
permissions:
12-
actions: read
1317
contents: read
14-
security-events: write
1518

1619
jobs:
1720
test:
@@ -32,9 +35,6 @@ jobs:
3235
- name: Install dependencies
3336
run: npm ci
3437

35-
- name: Audit dependencies
36-
run: npm audit --audit-level=moderate
37-
3838
- name: Type check
3939
run: npm run check
4040

@@ -77,23 +77,43 @@ jobs:
7777
sarif_file: guarddog.sarif
7878
category: guarddog
7979

80-
osv-scanner:
81-
name: OSV Scanner
82-
# google/osv-scanner-action v2.3.8
83-
# NOTE: the repo setting "Require actions pinned to a full-length commit SHA"
84-
# is intentionally OFF. v2.3.8's reusable workflow ships an unpinned
85-
# actions/download-artifact, which GitHub's SHA-pin enforcement rejects
86-
# recursively (breaks this job at startup). Fixed upstream on main
87-
# (osv-scanner-action commit f6fb127, not yet in a release). Re-enable the
88-
# setting once Dependabot bumps this pin past the next osv-scanner-action release.
89-
uses: google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@9a498708959aeaef5ef730655706c5a1df1edbc2
90-
with:
91-
scan-args: |-
92-
--lockfile=package-lock.json
80+
security-audit:
81+
name: Dependency Audit
82+
runs-on: ubuntu-24.04
83+
# Whole-tree CVE gate. Runs on push-to-main, the weekly schedule, and manual
84+
# dispatch — never on pull_request. This is deliberate: a freshly-published
85+
# advisory in (dev/build) tooling that a PR never touched should not red-X
86+
# every open PR. On PRs, vulnerabilities are gated by Dependency Review
87+
# (scoped to the deps the diff introduces) plus GuardDog; Dependabot alerts
88+
# and auto-PRs are the remediation path. The push-to-main run still gates the
89+
# deploy job below, so production protection is unchanged.
90+
if: github.event_name != 'pull_request'
91+
permissions:
92+
contents: read
93+
steps:
94+
- name: Checkout
95+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10
96+
97+
- name: Setup Node
98+
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
99+
with:
100+
node-version: 20
101+
cache: 'npm'
102+
103+
- name: Install dependencies
104+
run: npm ci
105+
106+
- name: Audit dependencies
107+
run: npm audit --audit-level=moderate
93108

94109
sbom:
95110
name: SBOM & Grype Scan
96111
runs-on: ubuntu-24.04
112+
# Off the PR path (same rationale as security-audit). Generates the SBOM
113+
# artifact and runs Grype for Security-tab visibility, but is non-blocking
114+
# (fail-build: false) — npm audit in security-audit is the single whole-tree
115+
# gate. Runs on push-to-main + schedule and feeds the deploy pipeline below.
116+
if: github.event_name != 'pull_request'
97117
permissions:
98118
contents: read
99119
security-events: write
@@ -114,7 +134,7 @@ jobs:
114134
uses: anchore/scan-action@e1165082ffb1fe366ebaf02d8526e7c4989ea9d2
115135
with:
116136
sbom: sbom.spdx.json
117-
fail-build: true
137+
fail-build: false
118138
severity-cutoff: medium
119139

120140
- name: Upload Grype SARIF
@@ -144,7 +164,7 @@ jobs:
144164
deploy:
145165
name: Deploy to Cloudflare Pages
146166
runs-on: ubuntu-24.04
147-
needs: [test, guarddog, osv-scanner, sbom]
167+
needs: [test, guarddog, security-audit, sbom]
148168
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
149169
environment: production
150170
permissions:

SECURITY.md

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ You can expect an acknowledgement within 48 hours and a resolution or status upd
3434
| Adversary | Goal | Primary controls |
3535
|---|---|---|
3636
| Malicious paste / shared link | Trigger XSS via crafted SAML or JWT content rendered in the DOM | Svelte's default text escaping, `esc()` in the XML highlighter, CSP (`default-src 'self'`, no `unsafe-inline`, hash-pinned inline scripts) |
37-
| Compromised npm package | Inject malicious code into the build or CI environment | GuardDog, OSV-Scanner, Grype, `npm audit`, SHA-pinned actions, `npm ci` lockfile enforcement |
37+
| Compromised npm package | Inject malicious code into the build or CI environment | GuardDog, `npm audit`, Grype, Dependency Review, SHA-pinned actions, `npm ci` lockfile enforcement |
3838
| Compromised upstream GitHub Action | Substitute malicious CI code via a tampered version tag | All actions pinned to commit SHA, Dependabot rotates pins daily |
3939
| SSRF via OIDC proxy | Use the discovery Worker to reach internal infrastructure | `redirect: 'error'`, 5-second timeout, 100 KB cap, response validation |
4040
| Compromised maintainer account | Push unsigned or unreviewed code to `main` | Required commit signatures, branch protection, CODEOWNERS review, scoped API tokens |
@@ -137,7 +137,6 @@ Current pins in `.github/workflows/ci.yml`:
137137
- `codecov/codecov-action` — SHA-pinned
138138
- `github/codeql-action/upload-sarif` — SHA-pinned (v3.28.13)
139139
- `actions/dependency-review-action` — SHA-pinned (v5.0.0)
140-
- `google/osv-scanner-action` reusable workflow — SHA-pinned (v2.3.8)
141140
- `anchore/sbom-action` — SHA-pinned (v0.24.0)
142141
- `anchore/scan-action` — SHA-pinned (v7.4.0)
143142
- `actions/attest-build-provenance` — SHA-pinned (v4.1.0)
@@ -160,13 +159,9 @@ Dependabot is configured to open daily PRs when new versions of these actions ar
160159

161160
### Minimal token permissions
162161

163-
All workflows use explicit `permissions` blocks rather than relying on GitHub's write-all default. The CI workflow sets the following at the workflow level, which serves as the baseline for all jobs:
162+
All workflows use explicit `permissions` blocks rather than relying on GitHub's write-all default. The CI workflow sets `contents: read` at the workflow level as the baseline for all jobs.
164163

165-
- `actions: read` — required for the OSV-Scanner reusable workflow (GitHub constrains reusable workflow callers: the calling workflow must include any permissions granted to the called workflow)
166-
- `contents: read`
167-
- `security-events: write` — required for SARIF upload to GitHub Code Scanning across multiple jobs; also needed at workflow level for the OSV-Scanner reusable workflow call
168-
169-
Individual jobs override this baseline to the minimum they require. The deploy job additionally requests `deployments: write` (Cloudflare Pages action), `id-token: write` (OIDC for sigstore attestation), and `attestations: write` (GitHub attestation API). No job can write to the repository, create issues, or make API calls beyond what is explicitly declared.
164+
Individual jobs add only what they require — the `GuardDog Supply Chain Scan` and `SBOM & Grype Scan` jobs each request `security-events: write` for their own SARIF upload. The deploy job additionally requests `deployments: write` (Cloudflare Pages action), `id-token: write` (OIDC for sigstore attestation), and `attestations: write` (GitHub attestation API). No job can write to the repository, create issues, or make API calls beyond what is explicitly declared.
170165

171166
### No secrets in PR workflows
172167

@@ -180,7 +175,9 @@ The deploy job targets a GitHub Environment named `production`. This provides a
180175

181176
### Dependency auditing
182177

183-
The CI workflow runs `npm audit --audit-level=moderate` on every push and pull request. The build fails if any moderate, high, or critical vulnerabilities are present in the transitive dependency tree. Dependencies are installed with `npm ci` (not `npm install`), which installs exactly what is recorded in `package-lock.json` and fails if there is any discrepancy — preventing lockfile drift and ensuring reproducible installs.
178+
`npm audit --audit-level=moderate` runs in a dedicated `Dependency Audit` job that fails the build if any moderate, high, or critical vulnerability is present anywhere in the transitive dependency tree. This job runs on push to `main`, on a weekly schedule (Mondays 06:00 UTC), and on manual dispatch — but **not** on pull requests. The rationale: a freshly-published advisory in build/test tooling that a PR never touched should not block every open pull request at once. Pull requests are vulnerability-gated by Dependency Review (scoped to the dependencies the diff introduces) and GuardDog; Dependabot alerts and auto-PRs drive remediation; and the push-to-`main` run still gates the production deploy, so deploy protection is unchanged.
179+
180+
Dependencies are installed with `npm ci` (not `npm install`), which installs exactly what is recorded in `package-lock.json` and fails if there is any discrepancy — preventing lockfile drift and ensuring reproducible installs.
184181

185182
### Secret scanning
186183

@@ -208,25 +205,21 @@ A CodeQL workflow runs on every push and pull request to `main`, and weekly on M
208205

209206
GuardDog runs entirely within the CI runner — no data is sent to any external service. Findings are reported as SARIF and uploaded to the GitHub code scanning dashboard. The job must pass before deploy is allowed.
210207

211-
### OSV-Scanner
212-
213-
[OSV-Scanner](https://google.github.io/osv-scanner/) (Google, Apache 2.0) runs on every push and pull request via Google's official reusable workflow. It queries the [Open Source Vulnerabilities](https://osv.dev) database, which aggregates CVE, GitHub Security Advisories (GHSA), and OSV records — a broader set of sources than npm's advisory feed alone. Findings are automatically uploaded to the GitHub code scanning dashboard as SARIF. The job must pass before deploy is allowed.
214-
215208
### SBOM and Grype
216209

217-
On every push and pull request:
210+
Alongside the `Dependency Audit` job (push, weekly schedule, and manual dispatch — not pull requests):
218211

219212
- **Syft** ([anchore/sbom-action](https://github.com/anchore/sbom-action), Apache 2.0) generates a Software Bill of Materials in SPDX-JSON format and uploads it as a workflow artifact. This provides an auditable inventory of every package in the build and supports compliance requirements that expect a machine-readable SBOM.
220213

221-
- **Grype** ([anchore/scan-action](https://github.com/anchore/scan-action), Apache 2.0) scans the Syft-generated SBOM for known vulnerabilities at medium severity or higher. Findings are uploaded to the GitHub code scanning dashboard as SARIF. The job must pass before deploy is allowed.
214+
- **Grype** ([anchore/scan-action](https://github.com/anchore/scan-action), Apache 2.0) scans the Syft-generated SBOM for known vulnerabilities at medium severity or higher and uploads findings to the GitHub code scanning dashboard as SARIF. Grype runs in non-blocking mode (`fail-build: false`): `npm audit` in the `Dependency Audit` job is the single blocking CVE gate, while Grype contributes a second, independent vulnerability database to the Security tab.
222215

223216
### Dependency Review
224217

225-
The [dependency-review-action](https://github.com/actions/dependency-review-action) runs on pull requests only. It compares the dependency diff introduced by the PR against GitHub's vulnerability database and fails the check if any newly added package carries a moderate or higher CVE. This catches vulnerable dependencies at PR time, before they land in `main`, complementing the full-tree scans that run on push.
218+
The [dependency-review-action](https://github.com/actions/dependency-review-action) runs on pull requests only. It compares the dependency diff introduced by the PR against GitHub's vulnerability database and fails the check if any newly added package carries a moderate or higher CVE. This is the pull-request-time vulnerability gate: it catches vulnerable dependencies a PR would *introduce*, before they land in `main`, while the full-tree `npm audit` and Grype scans run on push and the weekly schedule. A PR is therefore never blocked by a pre-existing, tree-wide advisory in a dependency the PR did not touch.
226219

227220
### GitHub Code Scanning
228221

229-
All SARIF-producing tools (CodeQL, GuardDog, OSV-Scanner, Grype, OSSF Scorecard) upload their findings to GitHub's code scanning dashboard (Security → Code scanning). This provides a single triage surface across all scanners, with per-file, per-line annotation on pull requests. Each tool registers under its own `category` so findings are de-duplicated and attributable to their source.
222+
All SARIF-producing tools (CodeQL, GuardDog, Grype, OSSF Scorecard) upload their findings to GitHub's code scanning dashboard (Security → Code scanning). This provides a single triage surface across all scanners, with per-file, per-line annotation on pull requests. Each tool registers under its own `category` so findings are de-duplicated and attributable to their source.
230223

231224
### OSSF Scorecard
232225

@@ -242,7 +235,7 @@ This means the provenance of every deployed bundle is verifiable: given the arti
242235

243236
The `main` branch is protected with the following rules enforced for all contributors:
244237

245-
- **Required status checks**`Build & Test`, `GuardDog Supply Chain Scan`, `OSV Scanner / osv-scan`, `SBOM & Grype Scan`, and `Dependency Review` must all pass before any merge is allowed; the branch must be up to date with `main` before merging (strict mode)
238+
- **Required status checks**`Build & Test`, `GuardDog Supply Chain Scan`, `Dependency Review`, and `CodeQL` must all pass before any merge is allowed; the branch must be up to date with `main` before merging (strict mode). The whole-tree scans (`Dependency Audit`, `SBOM & Grype Scan`) run on push and the weekly schedule rather than on pull requests, so they are not pull-request status checks — they gate the production deploy instead
246239
- **Required signatures** — every commit merged to `main` must carry a verified cryptographic signature
247240
- **Required pull request review** — at least one approval is required; stale approvals are dismissed on new pushes; code owner review is required
248241
- **No force pushes** — force-pushing to `main` is blocked

0 commit comments

Comments
 (0)