Skip to content

SPEC §9: peer-derived text in observer-facing errors (boundary unresolved) #3177

SPEC §9: peer-derived text in observer-facing errors (boundary unresolved)

SPEC §9: peer-derived text in observer-facing errors (boundary unresolved) #3177

Workflow file for this run

name: Security
on:
push:
branches: [main]
pull_request:
schedule:
# Weekly scan on Monday at 6am UTC
- cron: '0 6 * * 1'
workflow_dispatch:
permissions: {}
jobs:
trivy-go:
name: Trivy (Go SDK)
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# Full-severity SARIF for Security-tab visibility; the gate step below enforces policy.
# (In SARIF mode the action reports all severities unless limit-severities-for-sarif is set.)
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
scan-type: 'fs'
scan-ref: './go'
ignore-unfixed: true
format: 'sarif'
output: 'trivy-go-results.sarif'
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
if: always()
continue-on-error: true # Requires GitHub Advanced Security
with:
sarif_file: 'trivy-go-results.sarif'
category: 'trivy-go'
# Table mode passes the severity filter through, so this gates on HIGH/CRITICAL only.
- name: Fail on HIGH/CRITICAL vulnerabilities
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
env:
TRIVY_SKIP_DB_UPDATE: true
with:
scan-type: 'fs'
scan-ref: './go'
severity: 'HIGH,CRITICAL'
exit-code: '1'
ignore-unfixed: true
format: 'table'
skip-setup-trivy: true
cache: 'false'
# Ground truth, and the authoritative check: the static parser predicts
# that nothing writes a lockfile, this observes whether anything did. A CI
# checkout starts pristine, so the committed tree is the baseline. Catches
# a manifest that was modified AND one CREATED where none was tracked, and
# runs on failure too, so a job cannot fail and hide what it dirtied.
- name: Dependency manifests unchanged by this job
if: always()
working-directory: .
run: ./scripts/assert-lockfiles-unchanged --verify-clean
trivy-typescript:
name: Trivy (TypeScript SDK)
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# Full-severity SARIF for Security-tab visibility; the gate step below enforces policy.
# (In SARIF mode the action reports all severities unless limit-severities-for-sarif is set.)
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
scan-type: 'fs'
scan-ref: './typescript'
ignore-unfixed: true
format: 'sarif'
output: 'trivy-ts-results.sarif'
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
if: always()
continue-on-error: true # Requires GitHub Advanced Security
with:
sarif_file: 'trivy-ts-results.sarif'
category: 'trivy-typescript'
# Table mode passes the severity filter through, so this gates on HIGH/CRITICAL only.
- name: Fail on HIGH/CRITICAL vulnerabilities
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
env:
TRIVY_SKIP_DB_UPDATE: true
with:
scan-type: 'fs'
scan-ref: './typescript'
severity: 'HIGH,CRITICAL'
exit-code: '1'
ignore-unfixed: true
format: 'table'
skip-setup-trivy: true
cache: 'false'
# Ground truth, and the authoritative check: the static parser predicts
# that nothing writes a lockfile, this observes whether anything did. A CI
# checkout starts pristine, so the committed tree is the baseline. Catches
# a manifest that was modified AND one CREATED where none was tracked, and
# runs on failure too, so a job cannot fail and hide what it dirtied.
- name: Dependency manifests unchanged by this job
if: always()
working-directory: .
run: ./scripts/assert-lockfiles-unchanged --verify-clean
govulncheck:
name: Go Vulnerability Check
runs-on: ubuntu-latest
permissions:
contents: read
defaults:
run:
working-directory: go
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: 'go/go.mod'
cache-dependency-path: 'go/go.sum'
# Scan against the newest 1.26.x, not whatever patch the runner has cached.
check-latest: true
- name: Install govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@latest
- name: Run govulncheck
run: govulncheck ./...
# Ground truth, and the authoritative check: the static parser predicts
# that nothing writes a lockfile, this observes whether anything did. A CI
# checkout starts pristine, so the committed tree is the baseline. Catches
# a manifest that was modified AND one CREATED where none was tracked, and
# runs on failure too, so a job cannot fail and hide what it dirtied.
- name: Dependency manifests unchanged by this job
if: always()
working-directory: .
run: ./scripts/assert-lockfiles-unchanged --verify-clean
gosec:
name: Go Security Checker
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
defaults:
run:
working-directory: go
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: 'go/go.mod'
cache-dependency-path: 'go/go.sum'
- name: Install gosec
run: go install github.com/securego/gosec/v2/cmd/gosec@v2.23.0
- name: Run gosec
run: gosec -severity high -exclude-dir=pkg/generated -fmt sarif -out gosec-results.sarif ./...
- name: Upload gosec results
uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
if: always()
continue-on-error: true
with:
sarif_file: 'go/gosec-results.sarif'
category: 'gosec'
# Ground truth, and the authoritative check: the static parser predicts
# that nothing writes a lockfile, this observes whether anything did. A CI
# checkout starts pristine, so the committed tree is the baseline. Catches
# a manifest that was modified AND one CREATED where none was tracked, and
# runs on failure too, so a job cannot fail and hide what it dirtied.
- name: Dependency manifests unchanged by this job
if: always()
working-directory: .
run: ./scripts/assert-lockfiles-unchanged --verify-clean
npm-audit:
name: npm Audit (TypeScript SDK)
runs-on: ubuntu-latest
permissions:
contents: read
defaults:
run:
working-directory: typescript
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '24'
- name: Install dependencies
run: npm ci
- name: Run npm audit
run: npm audit --audit-level=high
# Ground truth, and the authoritative check: the static parser predicts
# that nothing writes a lockfile, this observes whether anything did. A CI
# checkout starts pristine, so the committed tree is the baseline. Catches
# a manifest that was modified AND one CREATED where none was tracked, and
# runs on failure too, so a job cannot fail and hide what it dirtied.
- name: Dependency manifests unchanged by this job
if: always()
working-directory: .
run: ./scripts/assert-lockfiles-unchanged --verify-clean
trivy-ruby:
name: Trivy (Ruby SDK)
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# Full-severity SARIF for Security-tab visibility; the gate step below enforces policy.
# (In SARIF mode the action reports all severities unless limit-severities-for-sarif is set.)
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
scan-type: 'fs'
scan-ref: './ruby'
ignore-unfixed: true
format: 'sarif'
output: 'trivy-ruby-results.sarif'
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
if: always()
continue-on-error: true
with:
sarif_file: 'trivy-ruby-results.sarif'
category: 'trivy-ruby'
# Table mode passes the severity filter through, so this gates on HIGH/CRITICAL only.
- name: Fail on HIGH/CRITICAL vulnerabilities
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
env:
TRIVY_SKIP_DB_UPDATE: true
with:
scan-type: 'fs'
scan-ref: './ruby'
severity: 'HIGH,CRITICAL'
exit-code: '1'
ignore-unfixed: true
format: 'table'
skip-setup-trivy: true
cache: 'false'
# Ground truth, and the authoritative check: the static parser predicts
# that nothing writes a lockfile, this observes whether anything did. A CI
# checkout starts pristine, so the committed tree is the baseline. Catches
# a manifest that was modified AND one CREATED where none was tracked, and
# runs on failure too, so a job cannot fail and hide what it dirtied.
- name: Dependency manifests unchanged by this job
if: always()
working-directory: .
run: ./scripts/assert-lockfiles-unchanged --verify-clean
trivy-python:
name: Trivy (Python SDK)
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# Full-severity SARIF for Security-tab visibility; the gate step below enforces policy.
# (In SARIF mode the action reports all severities unless limit-severities-for-sarif is set.)
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
scan-type: 'fs'
scan-ref: './python'
ignore-unfixed: true
format: 'sarif'
output: 'trivy-python-results.sarif'
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
if: always()
continue-on-error: true
with:
sarif_file: 'trivy-python-results.sarif'
category: 'trivy-python'
# Table mode passes the severity filter through, so this gates on HIGH/CRITICAL only.
- name: Fail on HIGH/CRITICAL vulnerabilities
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
env:
TRIVY_SKIP_DB_UPDATE: true
with:
scan-type: 'fs'
scan-ref: './python'
severity: 'HIGH,CRITICAL'
exit-code: '1'
ignore-unfixed: true
format: 'table'
skip-setup-trivy: true
cache: 'false'
# Ground truth, and the authoritative check: the static parser predicts
# that nothing writes a lockfile, this observes whether anything did. A CI
# checkout starts pristine, so the committed tree is the baseline. Catches
# a manifest that was modified AND one CREATED where none was tracked, and
# runs on failure too, so a job cannot fail and hide what it dirtied.
- name: Dependency manifests unchanged by this job
if: always()
working-directory: .
run: ./scripts/assert-lockfiles-unchanged --verify-clean
bundler-audit:
name: Bundler Audit (Ruby SDK)
runs-on: ubuntu-latest
permissions:
contents: read
defaults:
run:
working-directory: ruby
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up Ruby
uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
with:
ruby-version: '3.3'
bundler-cache: true
working-directory: ruby
- name: Install bundler-audit
run: gem install bundler-audit # zizmor: ignore[adhoc-packages] -- bundler-audit is the auditing tool itself; adding it to the SDK's Gemfile would make the gem under audit depend on its own auditor
- name: Update vulnerability database
run: bundle-audit update
- name: Run bundler-audit
run: bundle-audit check
# Ground truth, and the authoritative check: the static parser predicts
# that nothing writes a lockfile, this observes whether anything did. A CI
# checkout starts pristine, so the committed tree is the baseline. Catches
# a manifest that was modified AND one CREATED where none was tracked, and
# runs on failure too, so a job cannot fail and hide what it dirtied.
- name: Dependency manifests unchanged by this job
if: always()
working-directory: .
run: ./scripts/assert-lockfiles-unchanged --verify-clean
gradle-dependency-check:
name: Gradle Dependency Check (Kotlin SDK)
runs-on: ubuntu-latest
permissions:
contents: read
defaults:
run:
working-directory: kotlin
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up Java
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
distribution: 'temurin'
java-version: '17'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
with:
cache-provider: basic
- name: Run dependency verification
run: ./gradlew :basecamp-sdk:dependencies --scan
- name: Check for known vulnerabilities
run: ./gradlew :basecamp-sdk:dependencyInsight --dependency org.jetbrains.kotlin --scan || true
# Ground truth, and the authoritative check: the static parser predicts
# that nothing writes a lockfile, this observes whether anything did. A CI
# checkout starts pristine, so the committed tree is the baseline. Catches
# a manifest that was modified AND one CREATED where none was tracked, and
# runs on failure too, so a job cannot fail and hide what it dirtied.
- name: Dependency manifests unchanged by this job
if: always()
working-directory: .
run: ./scripts/assert-lockfiles-unchanged --verify-clean
swift-audit:
name: Swift Dependency Audit
runs-on: macos-15
permissions:
contents: read
defaults:
run:
working-directory: swift
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Audit Swift dependencies
run: |
swift package show-dependencies --format json > deps.json
echo "Swift package dependencies:"
cat deps.json | python3 -c "
import json, sys
deps = json.load(sys.stdin)
def walk(dep, depth=0):
name = dep.get('identity', dep.get('name', 'unknown'))
version = dep.get('version', 'unknown')
print(f\"{' ' * depth}{name}@{version}\")
for d in dep.get('dependencies', []):
walk(d, depth + 1)
walk(deps)
"
rm -f deps.json
# Ground truth, and the authoritative check: the static parser predicts
# that nothing writes a lockfile, this observes whether anything did. A CI
# checkout starts pristine, so the committed tree is the baseline. Catches
# a manifest that was modified AND one CREATED where none was tracked, and
# runs on failure too, so a job cannot fail and hide what it dirtied.
- name: Dependency manifests unchanged by this job
if: always()
working-directory: .
run: ./scripts/assert-lockfiles-unchanged --verify-clean
secrets:
name: Secret Scanning
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: Install and run gitleaks
env:
GITLEAKS_VERSION: '8.22.0'
run: |
TARBALL="gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
curl -sSfL -O "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/${TARBALL}"
curl -sSfL -O "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_checksums.txt"
sha256sum --check --ignore-missing "gitleaks_${GITLEAKS_VERSION}_checksums.txt"
tar xzf "${TARBALL}" gitleaks
./gitleaks detect --source . --no-git -v
dependency-review:
name: Dependency Review
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
continue-on-error: true # Requires GitHub Advanced Security
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0