Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/codeql/codeql-config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# api-test CodeQL configuration.
#
# Referenced from .github/workflows/codeql.yml via config-file. Without
# this, CodeQL uses the default suite plus no project-specific tuning.

name: "api-test CodeQL"

queries:
- uses: security-and-quality

# Repository-wide query filters. Each entry must justify why a query is
# excluded; "looks scary" is not a reason. The audit logger is the only
# legitimate "Log function with potentially sensitive data" sink in this
# project, and that's by design (forensics over discretion). Adding
# any new Log-named function in this codebase MUST be reviewed against
# this exception, since the rule no longer fires globally.
query-filters:
- exclude:
id: go/clear-text-logging
# Justification: audit.Logger.Log captures full audit_events
# rows (sanitized via redact_keys) by design. CodeQL traces
# err.Error() -> Event.ErrorMessage -> *ev -> Log() and flags
# the whole chain. The error message is what an operator NEEDS
# to see during incident review; suppressing it would defeat
# the audit pipeline. gosec and semgrep still cover other
# cleartext-credential-in-log patterns at the function-call
# level (e.g. fmt.Println, log.Print*).
189 changes: 189 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
name: CI

on:
push:
branches: [main]
pull_request:
# Skip CI on pure docs/markdown changes; the docs.yml workflow
# handles those and we don't want to burn CI minutes on them.
paths-ignore:
- "docs/**"
- "**.md"

# Cancel in-flight runs for the same ref. github.ref differs between
# `push` (refs/heads/X) and `pull_request` (refs/pull/N/merge), so
# grouping on ref alone leaves push+PR runs of the same SHA both alive.
# Use head_ref (branch name on PRs) when available so the two collapse.
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: true

# Workflow-level read-only baseline. Each job grants only what it needs.
permissions: read-all

jobs:
lint:
name: Lint
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: "1.26.3"
cache: true

- name: Run golangci-lint
uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0
with:
version: v2.11.4
args: --timeout=5m

test:
name: Test
runs-on: ubuntu-24.04
timeout-minutes: 20
permissions:
contents: read
# Lift CODECOV_TOKEN to job-level env so the step-level `if:`
# expression below can reference env.CODECOV_TOKEN. Step-level `if:`
# cannot read the `secrets` context directly.
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: "1.26.3"
cache: true

- name: Verify go.mod is tidy
run: |
go mod tidy
if ! git diff --quiet go.mod go.sum; then
echo "go.mod / go.sum are out of date; run 'go mod tidy' locally" >&2
git diff go.mod go.sum
exit 1
fi

# vet is covered by golangci-lint (govet enabled in .golangci.yml);
# don't double-run it here.

- name: Run tests with race + coverage
run: go test -race -count=1 -coverprofile=coverage.out -covermode=atomic ./...

- name: Coverage gate (>=80%)
run: ./scripts/coverage-gate.sh coverage.out 80

# Codecov upload skips when CODECOV_TOKEN isn't configured so the
# step doesn't pretend to upload anything. Set the secret in repo
# settings to enable per-PR coverage diff reporting.
- name: Upload coverage to Codecov
if: ${{ env.CODECOV_TOKEN != '' }}
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
with:
files: ./coverage.out
fail_ci_if_error: false
verbose: true

build:
name: Build
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: "1.26.3"
cache: true

- name: Build
run: go build -v ./...

- name: Verify dependencies
run: go mod verify

# frontend: not yet present. The React 19 + Vite + Tailwind 4 SPA lands
# in M3; once ui/ exists with package.json + pnpm-lock.yaml, copy
# mcp-test's frontend job verbatim (pnpm install + tsc --noEmit + build).

security:
name: Security
runs-on: ubuntu-24.04
timeout-minutes: 15
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: "1.26.3"
cache: true

- name: gosec
run: |
go install github.com/securego/gosec/v2/cmd/gosec@v2.25.0
gosec ./...

- name: govulncheck
uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # v1.0.4
with:
go-version-input: "1.26.3"
repo-checkout: false

- name: Semgrep
# `semgrep/semgrep-action@v1` exits 0 even when findings are
# present (it's designed to upload to AppSec Platform when
# SEMGREP_APP_TOKEN is set, otherwise just print). We need it
# to FAIL the job on findings so CI matches the local
# `make semgrep` gate (which uses --error). Run the CLI
# directly. Version is pinned (mirrors $(SEMGREP_VERSION) in
# the Makefile); --break-system-packages is required on
# ubuntu-24.04's PEP 668 system Python.
run: |
python3 -m pip install --quiet --break-system-packages 'semgrep==1.110.0'
semgrep scan --error --quiet --config p/golang --config .semgrep/

integration:
name: Integration tests
runs-on: ubuntu-24.04
timeout-minutes: 20
permissions:
contents: read
# Integration tests use testcontainers (Docker socket on the GitHub
# runner). Tagged `integration` so they don't fire in the default
# `go test ./...` invocation. Runs on every PR and push to main;
# narrow with `paths:` filters once the cost of unconditional runs
# outweighs the safety of always exercising them.
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: "1.26.3"
cache: true

- name: Run integration tests
# Narrow to ./tests/... so we don't rebuild every package with
# the integration tag and rerun unrelated unit tests. New
# integration-tagged tests should live under tests/ to stay
# in scope.
run: go test -tags=integration -race -count=1 -timeout=10m ./tests/...
55 changes: 55 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: CodeQL

# This is the project's authoritative CodeQL configuration. If GitHub's
# default-config CodeQL is also enabled (Settings -> Code security ->
# Code scanning -> CodeQL analysis -> "Default"), every PR will run two
# identical scans and bill twice. Set the default to "None" or "Custom"
# pointing at this workflow to dedupe.

on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
# Weekly on Monday at 06:00 UTC. Catches new query updates and any
# vulnerabilities introduced via dependencies that no PR-time scan
# would have surfaced.
- cron: "0 6 * * 1"

permissions: read-all

jobs:
analyze:
name: Analyze
runs-on: ubuntu-24.04
timeout-minutes: 30
permissions:
security-events: write
contents: read
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: "1.26.3"
cache: true

- name: Initialize CodeQL
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
with:
languages: go
# security-and-quality bundles the security pack with style /
# correctness rules. Project-specific query exclusions live
# in the config-file; findings post to the repo's Security tab.
config-file: ./.github/codeql/codeql-config.yml

- name: Autobuild
uses: github/codeql-action/autobuild@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
with:
category: "/language:go"
59 changes: 59 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Deploy Documentation

on:
push:
branches:
- main
paths:
- "docs/**"
- "mkdocs.yml"
- ".github/workflows/docs.yml"
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: "pages"
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Setup Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.12"

- name: Install dependencies
run: |
pip install \
mkdocs-material \
'pymdown-extensions>=10.0'

- name: Build documentation
run: mkdocs build --strict

- name: Upload artifact
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1
with:
path: site

deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-24.04
timeout-minutes: 10
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5
58 changes: 58 additions & 0 deletions .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: OpenSSF Scorecard

# `publish_results: true` requires the repo to be public; the Scorecard
# workflow will hard-fail on a private fork. If this repo is ever
# flipped to private, drop publish_results and remove the upload-sarif
# step (or migrate to scorecard's API token mode).

on:
branch_protection_rule:
schedule:
# Saturday 01:30 UTC; offset from CodeQL's Monday cron so the two
# don't queue together when the runner pool is hot.
- cron: "30 1 * * 6"
push:
branches: [main]

permissions: read-all

jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-24.04
timeout-minutes: 15
permissions:
# Job-level permissions REPLACE the workflow-level read-all
# baseline; list every grant scorecard needs.
# security-events: write — upload SARIF to code-scanning
# id-token: write — sign the SLSA provenance
# contents: read — clone the repo and read workflow files
# actions: read — Token-Permissions check inspects other workflows
security-events: write
id-token: write
contents: read
actions: read
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Run analysis
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
with:
results_file: results.sarif
results_format: sarif
publish_results: true

- name: Upload SARIF artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: SARIF file
path: results.sarif
retention-days: 5

- name: Upload to code-scanning
uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
with:
sarif_file: results.sarif
Loading
Loading