Merge pull request #17 from TheZwiss/security/scanning-pipeline

Security scanning pipeline (report-only)
This commit is contained in:
TheZwiss
2026-07-13 11:26:27 +02:00
committed by GitHub
11 changed files with 347 additions and 17 deletions
+44
View File
@@ -0,0 +1,44 @@
# Dependabot keeps dependencies and CI actions patched. Three ecosystems:
# - npm → the pnpm workspace (Dependabot reads pnpm-lock.yaml v9)
# - github-actions → action version bumps (feeds the SHA-pin comments)
# - docker → the Dockerfile base image (FROM node:20-slim)
#
# NOTE (intentional): there is NO docker entry for docker-compose.yml. It sits
# at the same "/" directory (a second docker entry would collide on
# ecosystem+directory), and Dependabot's docker ecosystem parses Dockerfiles,
# not `image:` refs in compose. The pinned caddy / livekit-server compose images
# are updated MANUALLY — see the maintainer checklist in
# docs/systems/security-scanning.md.
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 10
groups:
# One grouped PR for routine minor/patch bumps to cut PR noise.
npm-minor-patch:
update-types:
- minor
- patch
ignore:
# uiohook-napi is pinned by an exact-version pnpm patch
# (patches/uiohook-napi@1.5.5.patch). A bump makes the patch path stop
# matching, breaking `pnpm install --frozen-lockfile` in CI and both
# Docker stages until the patch is regenerated. Bump it by hand.
- dependency-name: uiohook-napi
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
groups:
github-actions:
patterns:
- "*"
- package-ecosystem: docker
directory: /
schedule:
interval: weekly
+8 -3
View File
@@ -31,15 +31,20 @@ jobs:
node-version: [20, 24] node-version: [20, 24]
steps: steps:
- name: Harden the runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- name: Checkout - name: Checkout
uses: actions/checkout@v5 uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@v5 uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
with: with:
version: 10.34.3 version: 10.34.3
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v5 uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: pnpm cache: pnpm
+1 -1
View File
@@ -29,7 +29,7 @@ jobs:
(github.event.comment.body == 'recheck' || (github.event.comment.body == 'recheck' ||
github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') ||
github.event_name == 'pull_request_target' github.event_name == 'pull_request_target'
uses: contributor-assistant/github-action@v2.6.1 uses: contributor-assistant/github-action@ca4a40a7d1004f18d9960b404b97e5f30a505a08 # v2.6.1
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
+48
View File
@@ -0,0 +1,48 @@
name: CodeQL
# Static application security testing for all TS/JS. Uses build-mode: none — no
# compile needed, which sidesteps the monorepo/native-module build entirely.
# Default (code-scanning) query suite; security-extended is deferred (triage tax).
# CodeQL uploads alerts to the Security tab but does NOT fail the PR by itself —
# blocking is a repo setting (code-scanning merge protection), documented in the
# maintainer checklist in docs/systems/security-scanning.md.
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '27 3 * * 1' # weekly Monday 03:27 UTC
permissions:
contents: read
concurrency:
group: codeql-${{ github.ref }}
cancel-in-progress: true
jobs:
analyze:
name: Analyze (javascript-typescript)
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write # upload SARIF to code scanning
actions: read
steps:
- name: Harden the runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
- name: Initialize CodeQL
uses: github/codeql-action/init@02c5e83432fe5497fd85b873b6c9f16a8578e1d9 # v3.37.0
with:
languages: javascript-typescript
build-mode: none
- name: Perform CodeQL analysis
uses: github/codeql-action/analyze@02c5e83432fe5497fd85b873b6c9f16a8578e1d9 # v3.37.0
with:
category: "/language:javascript-typescript"
+4 -4
View File
@@ -24,10 +24,10 @@ jobs:
name: github-pages name: github-pages
url: ${{ steps.deployment.outputs.page_url }} url: ${{ steps.deployment.outputs.page_url }}
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
- uses: actions/configure-pages@v6 - uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
- uses: actions/upload-pages-artifact@v5 - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with: with:
path: ./site path: ./site
- id: deployment - id: deployment
uses: actions/deploy-pages@v5 uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
+6 -6
View File
@@ -33,7 +33,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v5 uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
# The runtime image bakes the git commit for the AGPL-3.0 § 13 source # The runtime image bakes the git commit for the AGPL-3.0 § 13 source
# offer (config.commit → GET /api/instance/info). The .git dir is not in # offer (config.commit → GET /api/instance/info). The .git dir is not in
@@ -44,13 +44,13 @@ jobs:
run: echo "commit=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" run: echo "commit=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT"
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- name: Log in to GitHub Container Registry - name: Log in to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
@@ -58,7 +58,7 @@ jobs:
- name: Derive image tags and labels - name: Derive image tags and labels
id: docker_meta id: docker_meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
with: with:
# github.repository is "TheZwiss/backspace"; metadata-action lowercases # github.repository is "TheZwiss/backspace"; metadata-action lowercases
# it → ghcr.io/thezwiss/backspace (GHCR requires lowercase). # it → ghcr.io/thezwiss/backspace (GHCR requires lowercase).
@@ -77,7 +77,7 @@ jobs:
org.opencontainers.image.revision=${{ github.sha }} org.opencontainers.image.revision=${{ github.sha }}
- name: Build and push (linux/amd64, linux/arm64) - name: Build and push (linux/amd64, linux/arm64)
uses: docker/build-push-action@v6 uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
with: with:
context: . context: .
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
+9 -3
View File
@@ -34,8 +34,14 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- name: Harden the runner
if: runner.os == 'Linux'
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- name: Checkout - name: Checkout
uses: actions/checkout@v5 uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
- name: Install Linux build dependencies - name: Install Linux build dependencies
if: runner.os == 'Linux' if: runner.os == 'Linux'
@@ -65,12 +71,12 @@ jobs:
sudo gem install --no-document fpm sudo gem install --no-document fpm
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@v5 uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
with: with:
version: 10.34.3 version: 10.34.3
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v5 uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with: with:
node-version: 20 node-version: 20
cache: pnpm cache: pnpm
+48
View File
@@ -0,0 +1,48 @@
name: OpenSSF Scorecard
# Scores the repo's security posture (branch protection, pinned deps, token
# permissions, etc.) and publishes to the OpenSSF public API so a badge can be
# shown (badge is added in Plan E). REQUIRES the canonical repo to be PUBLIC —
# see the maintainer checklist in docs/systems/security-scanning.md.
on:
branch_protection_rule:
schedule:
- cron: '18 4 * * 2' # weekly Tuesday 04:18 UTC
push:
branches: [main]
permissions: read-all
jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-latest
permissions:
security-events: write # upload SARIF
id-token: write # publish_results OIDC attestation
steps:
- name: Harden the runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
persist-credentials: false
- name: Run Scorecard
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
with:
results_file: results.sarif
results_format: sarif
publish_results: true
- name: Upload artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: SARIF file
path: results.sarif
retention-days: 5
- name: Upload SARIF to code scanning
uses: github/codeql-action/upload-sarif@02c5e83432fe5497fd85b873b6c9f16a8578e1d9 # v3.37.0
with:
sarif_file: results.sarif
+123
View File
@@ -0,0 +1,123 @@
name: Security
# Report-only in this plan: every scanner is non-blocking and uploads SARIF to
# the Security tab. Enforcement (fail on fixable HIGH/CRITICAL, block on secrets)
# is flipped on in Plan E after the remediation pass.
on:
pull_request:
push:
branches: [main]
schedule:
- cron: '32 5 * * 1' # weekly Monday 05:32 UTC
permissions:
contents: read
concurrency:
group: security-${{ github.ref }}
cancel-in-progress: true
jobs:
gitleaks:
name: Secret scan (gitleaks)
runs-on: ubuntu-latest
steps:
- name: Harden the runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- name: Checkout (full history)
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
fetch-depth: 0 # gitleaks scans the whole git history, not just the diff
- name: Run gitleaks
uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2.3.9
continue-on-error: true # report-only; enforcement flipped on in Plan E
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
osv-scanner:
name: Dependency scan (OSV-Scanner)
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write # upload SARIF to code scanning
steps:
- name: Harden the runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
- name: Run OSV-Scanner
uses: google/osv-scanner-action/osv-scanner-action@9a498708959aeaef5ef730655706c5a1df1edbc2 # v2.3.8
continue-on-error: true # report-only; enforcement flipped on in Plan E
with:
scan-args: |-
--lockfile=./pnpm-lock.yaml
--format=sarif
--output=osv-results.sarif
- name: Upload OSV SARIF
if: always()
uses: github/codeql-action/upload-sarif@02c5e83432fe5497fd85b873b6c9f16a8578e1d9 # v3.37.0
with:
sarif_file: osv-results.sarif
category: osv-scanner
trivy-config:
name: IaC/config scan (Trivy)
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- name: Harden the runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
- name: Trivy config scan (Dockerfile + docker-compose)
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
continue-on-error: true # report-only; enforcement flipped on in Plan E
with:
scan-type: config
scan-ref: .
format: sarif
output: trivy-config.sarif
- name: Upload Trivy config SARIF
if: always()
uses: github/codeql-action/upload-sarif@02c5e83432fe5497fd85b873b6c9f16a8578e1d9 # v3.37.0
with:
sarif_file: trivy-config.sarif
category: trivy-config
trivy-license:
name: License compliance scan (Trivy)
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- name: Harden the runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
- name: Trivy license scan
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
continue-on-error: true # report-only; enforcement flipped on in Plan E
with:
scan-type: fs
scan-ref: .
scanners: license
format: sarif
output: trivy-license.sarif
- name: Upload Trivy license SARIF
if: always()
uses: github/codeql-action/upload-sarif@02c5e83432fe5497fd85b873b6c9f16a8578e1d9 # v3.37.0
with:
sarif_file: trivy-license.sarif
category: trivy-license
+1
View File
@@ -160,6 +160,7 @@ Before modifying any subsystem, read its spec from `docs/systems/`. After making
| [message-list.md](docs/systems/message-list.md) | Auto-scroll model, position memory (session-only), embed renderer dimension contract, known limitations | Touching MessageList.tsx, scroll behavior, embed renderers, position restore | | [message-list.md](docs/systems/message-list.md) | Auto-scroll model, position memory (session-only), embed renderer dimension contract, known limitations | Touching MessageList.tsx, scroll behavior, embed renderers, position restore |
| [deployment.md](docs/systems/deployment.md) | Hosting pipeline: Docker/Caddy build, admin bootstrap, DB backup/restore, image pinning, env vars | Any deploy, backup/restore, or hosting change | | [deployment.md](docs/systems/deployment.md) | Hosting pipeline: Docker/Caddy build, admin bootstrap, DB backup/restore, image pinning, env vars | Any deploy, backup/restore, or hosting change |
| [activity-presence.md](docs/systems/activity-presence.md) | Presence states, rich activities, activity types/priorities, broadcast pipeline, visibility control, ActivityCard/Panel | Presence, rich activities, activity display, status management | | [activity-presence.md](docs/systems/activity-presence.md) | Presence states, rich activities, activity types/priorities, broadcast pipeline, visibility control, ActivityCard/Panel | Presence, rich activities, activity display, status management |
| [security-scanning.md](docs/systems/security-scanning.md) | CI security pipeline: Dependabot, CodeQL SAST, gitleaks, OSV-Scanner, Trivy (config/license; image scan in a later plan), OpenSSF Scorecard, SHA-pinning, harden-runner, tiered enforcement policy, maintainer settings checklist | Any CI security work, adding/changing scanners, enabling enforcement, supply-chain hardening |
--- ---
+55
View File
@@ -0,0 +1,55 @@
# Security Scanning & Supply-Chain Assurance
Automated, continuous scanning wired into GitHub Actions. This document is the
reference for what runs, where results go, and the one-time settings a maintainer
must enable. **Current state: report-only** — scanners surface findings in the
Security tab but do not block merges yet. Enforcement (blocking) is turned on in a
later change once the remediation pass has cleared the backlog.
## Workflows
| File | Purpose | Trigger | Result |
|------|---------|---------|--------|
| `.github/dependabot.yml` | Dependency + action + base-image update PRs | weekly | PRs |
| `.github/workflows/codeql.yml` | CodeQL SAST (`javascript-typescript`, build-mode none) | PR + push main + weekly | Security tab |
| `.github/workflows/security.yml` | gitleaks (secrets, full history), OSV-Scanner (deps), Trivy config (IaC), Trivy license | PR + push main + weekly | Security tab |
| `.github/workflows/scorecard.yml` | OpenSSF Scorecard (repo posture) | push main + weekly + on branch-protection change | Security tab + public badge |
> **gitleaks findings** surface in the workflow's job log and PR summary — the
> `gitleaks` job does not upload SARIF, so secret hits do **not** appear under
> Security → Code scanning (unlike the OSV / Trivy / CodeQL / Scorecard jobs).
## Tiered policy (target, enforced in a later change)
- **Always block:** gitleaks secret hit; OSV/Trivy fixable HIGH/CRITICAL; Trivy
disallowed license.
- **Advisory (SARIF → Security tab):** CodeQL alerts; OSV/Trivy unfixable or
medium/low; Scorecard.
Code-level gates (OSV, Trivy, gitleaks) block via workflow exit codes. CodeQL
merge-blocking, Dependabot alerts, and native secret-scanning are GitHub *settings*
— see the checklist below.
## Supply-chain hardening
- Every action is pinned to a full commit SHA (`# vX.Y.Z` comment) — resists
tag-move attacks and satisfies Scorecard's Pinned-Dependencies check.
- `step-security/harden-runner` (egress-policy `audit`) on Linux jobs.
- Least-privilege `permissions:` per workflow/job.
- SBOM + SLSA provenance **will be** attached to the published container image
(added with the container-image-scan work in a later plan — not yet live).
## Maintainer checklist (one-time GitHub settings — NOT code)
- [ ] Repository must be **public** (required for the Scorecard badge/publish and
the CodeQL free tier).
- [ ] Settings → Code security: enable **Dependabot alerts** and **Dependabot
security updates**.
- [ ] Settings → Code security: enable **Secret scanning** + **Push protection**.
- [ ] Settings → Code security: enable **CodeQL / code-scanning merge protection**
so high-severity alerts block PRs (the code-level gates do the rest).
- [ ] Branch protection on `main`: require the CI + security status checks to pass.
- [ ] **Manual image bumps:** Dependabot does not track `docker-compose.yml`
`image:` pins — update `caddy` and `livekit/livekit-server` by hand when new
releases ship. (Renovate, which parses compose, is an optional future
alternative.)