Merge pull request #17 from TheZwiss/security/scanning-pipeline
Security scanning pipeline (report-only)
This commit is contained in:
@@ -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
|
||||
@@ -31,15 +31,20 @@ jobs:
|
||||
node-version: [20, 24]
|
||||
|
||||
steps:
|
||||
- name: Harden the runner
|
||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v5
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
with:
|
||||
version: 10.34.3
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: pnpm
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
(github.event.comment.body == 'recheck' ||
|
||||
github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') ||
|
||||
github.event_name == 'pull_request_target'
|
||||
uses: contributor-assistant/github-action@v2.6.1
|
||||
uses: contributor-assistant/github-action@ca4a40a7d1004f18d9960b404b97e5f30a505a08 # v2.6.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
||||
@@ -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"
|
||||
@@ -24,10 +24,10 @@ jobs:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/configure-pages@v6
|
||||
- uses: actions/upload-pages-artifact@v5
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
|
||||
- uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
|
||||
with:
|
||||
path: ./site
|
||||
- id: deployment
|
||||
uses: actions/deploy-pages@v5
|
||||
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- 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
|
||||
# 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"
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
|
||||
|
||||
- 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
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
|
||||
- name: Derive image tags and labels
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
|
||||
with:
|
||||
# github.repository is "TheZwiss/backspace"; metadata-action lowercases
|
||||
# it → ghcr.io/thezwiss/backspace (GHCR requires lowercase).
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
org.opencontainers.image.revision=${{ github.sha }}
|
||||
|
||||
- name: Build and push (linux/amd64, linux/arm64)
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
@@ -34,8 +34,14 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Harden the runner
|
||||
if: runner.os == 'Linux'
|
||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
|
||||
- name: Install Linux build dependencies
|
||||
if: runner.os == 'Linux'
|
||||
@@ -65,12 +71,12 @@ jobs:
|
||||
sudo gem install --no-document fpm
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v5
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
with:
|
||||
version: 10.34.3
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: pnpm
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 |
|
||||
| [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 |
|
||||
| [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 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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.)
|
||||
Reference in New Issue
Block a user