Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
2c4fe53
Authorize every gateway API route; scope API tokens to their own perm…
ndbroadbent Oct 9, 2026
d72f7dc
Close MFA bypass via factor enrollment from a pre-MFA session
ndbroadbent Oct 9, 2026
e4e5bc8
Reject env var names and values that can break the KEY=VALUE format
ndbroadbent Oct 9, 2026
918c39b
Merge branch 'nathan/security-mfa-pending' into nathan/security-authz
ndbroadbent Oct 9, 2026
d648908
Proxy: forward an allowlist of client headers and set the rack actor
ndbroadbent Oct 9, 2026
2d97393
Scrub credentials from Sentry events; refuse test-only switches in pr…
ndbroadbent Oct 9, 2026
54f8cdd
Merge branch 'nathan/security-header-allowlist' into nathan/security-…
ndbroadbent Oct 9, 2026
c7583f8
ci: pin actions by SHA, verify installer checksums, least-privilege t…
ndbroadbent Oct 9, 2026
0a669fe
docker: pin base images by digest, run the gateway as non-root, drop …
ndbroadbent Oct 9, 2026
49cc881
Require verified, hosted-domain Google identities; RS256 only
ndbroadbent Oct 9, 2026
46bf350
Fix approval list MFA level and E2E fallout from the MFA session gate
ndbroadbent Oct 9, 2026
1a5cec2
CLI: focus the PIN field in the macOS security key dialog
ndbroadbent Oct 9, 2026
31424c5
Bind deploy approvals to the approved commit and artifact
ndbroadbent Oct 9, 2026
f1ccd58
Refuse websocket redirects that downgrade wss to ws
ndbroadbent Oct 9, 2026
547a6a8
CLI login: RFC 8252 loopback flow bound to the approving browser
ndbroadbent Oct 9, 2026
cd8e7df
Store WebAuthn challenges server-side; verify gateway TLS in the CLI
ndbroadbent Oct 9, 2026
19a114e
release: tag-only releases, immutable image tags, attestations
ndbroadbent Oct 9, 2026
b9ad537
ci: stop TruffleHog self-updating the pinned binary
ndbroadbent Oct 9, 2026
b791e00
Proxy: refuse unknown query parameters; cap approval tokens at owner …
ndbroadbent Oct 9, 2026
8cb80ab
Clear MFA verification on other sessions at first enrollment
ndbroadbent Oct 9, 2026
bb1d867
Accept backup codes in the web UI; verify the browser on CLI login MFA
ndbroadbent Oct 9, 2026
11acd99
Self-service for non-admin roles; route table checked against the router
ndbroadbent Oct 9, 2026
0f2407e
Keep agent worktrees out of build contexts; close stdin for E2E CLI c…
ndbroadbent Oct 9, 2026
d3db28f
Address DeepSource and CodeRabbit findings on the review fixes
ndbroadbent Oct 9, 2026
0f331cc
Merge branch 'nathan/security-authz' into nathan/security-approval-bi…
ndbroadbent Oct 9, 2026
4ec0ea0
Review fixes for deploy approval binding
ndbroadbent Oct 9, 2026
d3b4f07
Merge branch 'nathan/security-authz' into nathan/security-cli-loopback
ndbroadbent Oct 9, 2026
eb440ab
Merge branch 'nathan/security-authz' into nathan/security-webauthn
ndbroadbent Oct 9, 2026
779a679
E2E db helper: plain strings for SQL without interpolation
ndbroadbent Oct 9, 2026
084c03e
Review fixes for the loopback CLI login
ndbroadbent Oct 9, 2026
1e728ad
Review fixes for WebAuthn challenges and CLI TLS
ndbroadbent Oct 9, 2026
fa0b218
Merge branch 'nathan/security-cli-loopback' into nathan/security-weba…
ndbroadbent Oct 9, 2026
beca604
Review fixes for release hardening
ndbroadbent Oct 9, 2026
e036ac7
Bind CLI-login WebAuthn to the approving browser; reconcile with #22
ndbroadbent Oct 9, 2026
6d1530e
Merge branch 'nathan/security-approval-binding' into nathan/security-…
ndbroadbent Oct 9, 2026
c9411d7
Merge branch 'nathan/release-hardening' into nathan/security-audit-re…
ndbroadbent Oct 9, 2026
e1190dd
Merge branch 'nathan/pinentry-focus' into nathan/security-audit-release
ndbroadbent Oct 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ docs
# other repos checked out for reference
reference/

# Local agent state (Claude Code settings and worktrees)
.claude/

# Web app artifacts
web/node_modules/
web/dist
Expand Down
28 changes: 28 additions & 0 deletions .github/actions/install-convox/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Install Convox CLI
description: Install a pinned, checksum-verified Convox CLI (linux amd64) into /usr/local/bin.

inputs:
version:
description: Convox CLI release version
default: "3.25.7"
sha256:
description: SHA-256 of the convox-linux asset for that version
default: 6a0ffe6faf269302c311c2f8e4d1cdc116902c933c7d91f461c47d8a25190759

runs:
using: composite
steps:
- name: Install Convox CLI
shell: bash
env:
CONVOX_VERSION: ${{ inputs.version }}
CONVOX_SHA256: ${{ inputs.sha256 }}
run: |
set -euo pipefail
tmp="$(mktemp -d)"
curl -fsSL -o "${tmp}/convox" \
"https://github.com/convox/convox/releases/download/${CONVOX_VERSION}/convox-linux"
echo "${CONVOX_SHA256} ${tmp}/convox" | sha256sum --check --strict
sudo install -m 0755 "${tmp}/convox" /usr/local/bin/convox
rm -rf "${tmp}"
convox version || true
29 changes: 29 additions & 0 deletions .github/actions/install-task/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Install Task
description: Install a pinned, checksum-verified Task (taskfile.dev) binary into /usr/local/bin.

inputs:
version:
description: Task release version
default: "3.53.1"
sha256:
description: SHA-256 of task_linux_amd64.tar.gz for that version (from task_checksums.txt)
default: a54a408f6861ff921f6e87774180db31bacd8c1e7c944ca696db9fea49a82fc7

runs:
using: composite
steps:
- name: Install Task
shell: bash
env:
TASK_VERSION: ${{ inputs.version }}
TASK_SHA256: ${{ inputs.sha256 }}
run: |
set -euo pipefail
tmp="$(mktemp -d)"
curl -fsSL -o "${tmp}/task.tar.gz" \
"https://github.com/go-task/task/releases/download/v${TASK_VERSION}/task_linux_amd64.tar.gz"
echo "${TASK_SHA256} ${tmp}/task.tar.gz" | sha256sum --check --strict
tar -xzf "${tmp}/task.tar.gz" -C "${tmp}" task
sudo install -m 0755 "${tmp}/task" /usr/local/bin/task
rm -rf "${tmp}"
task --version
43 changes: 43 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
version: 2

updates:
- package-ecosystem: gomod
directory: /
schedule:
interval: weekly
ignore:
# Replaced by the local modules in internal/shims (see go.mod replace directives)
- dependency-name: github.com/docker/docker
- dependency-name: github.com/moby/buildkit
groups:
go-minor-patch:
update-types: [minor, patch]

- package-ecosystem: bun
directories:
- /web
- /docs
- /mock-oauth
schedule:
interval: weekly
groups:
bun-minor-patch:
update-types: [minor, patch]

- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
groups:
actions-minor-patch:
update-types: [minor, patch]

- package-ecosystem: docker
directories:
- /
- /mock-oauth
schedule:
interval: weekly
groups:
docker-minor-patch:
update-types: [minor, patch]
7 changes: 5 additions & 2 deletions .github/wait-for-checks.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,16 @@ module.exports = async function waitForChecks({
);

for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
const { data } = await github.rest.checks.listForRef({
// Paginate: scheduled workflows (e.g. the daily security scan) add check runs to the same commit,
// which can push the required ones off the first page.
const allRuns = await github.paginate(github.rest.checks.listForRef, {
owner,
repo,
ref,
per_page: 100,
});

const runs = data.check_runs.filter((run) => checkSet.has(run.name));
const runs = allRuns.filter((run) => checkSet.has(run.name));

if (runs.length === checkSet.size) {
const incomplete = runs.filter((run) => run.status !== "completed");
Expand Down
79 changes: 34 additions & 45 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ on:
pull_request:
branches: [main]

permissions:
contents: read

jobs:
go-tests:
runs-on: ubuntu-latest
Expand All @@ -14,10 +17,10 @@ jobs:
TEST_DATABASE_URL: postgres://postgres:postgres@localhost:55432/gateway_test?sslmode=disable
GOLANGCI_LINT_VERSION: v2.11.1
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
with:
go-version: "1.26.9"

Expand All @@ -27,25 +30,13 @@ jobs:
sudo apt-get install -y libfido2-dev libudev-dev pkg-config

- name: Install Task
run: |
curl -sL https://taskfile.dev/install.sh | sh -s -- -b /usr/local/bin
task --version
uses: ./.github/actions/install-task

- name: Go deps
run: task go:deps

- name: Install Convox CLI
run: |
set -euo pipefail
ARCH=$(uname -m)
URL="https://github.com/convox/convox/releases/latest/download/convox-linux"
if [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then
URL="https://github.com/convox/convox/releases/latest/download/convox-linux-arm64"
fi
curl -fsSL "$URL" -o /tmp/convox
sudo mv /tmp/convox /usr/local/bin/convox
sudo chmod 755 /usr/local/bin/convox
convox version || true
uses: ./.github/actions/install-convox

- name: Install Go tools
run: task go:tools
Expand All @@ -58,15 +49,15 @@ jobs:
env:
GOLANGCI_LINT_VERSION: v2.11.1
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
with:
go-version: "1.26.9"

- name: Cache golangci-lint cache
uses: actions/cache@v4
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: |
~/.cache/golangci-lint
Expand All @@ -78,24 +69,15 @@ jobs:
sudo apt-get install -y libfido2-dev libudev-dev pkg-config

- name: Install Task
run: |
curl -sL https://taskfile.dev/install.sh | sh -s -- -b /usr/local/bin
task --version

- name: Install golangci-lint
run: |
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh \
| sudo sh -s -- -b /usr/local/bin "${GOLANGCI_LINT_VERSION}"
golangci-lint version
uses: ./.github/actions/install-task

- name: Go deps (lint warmup)
run: task go:deps

- name: Verify golangci-lint config
run: task go:lint:config

# The action installs the pinned golangci-lint release and verifies .golangci.yml
# against its JSON schema (verify: true) before linting.
- name: golangci-lint
uses: golangci/golangci-lint-action@v8
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
with:
version: ${{ env.GOLANGCI_LINT_VERSION }}
env:
Expand All @@ -121,30 +103,41 @@ jobs:
run: task shellcheck

- name: Install govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@latest
run: go install golang.org/x/vuln/cmd/govulncheck@v1.8.0

- name: Check for vulnerabilities
run: task go:sec:vuln

- name: Install TruffleHog
env:
TRUFFLEHOG_VERSION: "3.97.9"
TRUFFLEHOG_SHA256: 40377e6572495412fb9ba0bc21c9401f73b72f1d2afd11b9931bc4a5ed622866
run: |
curl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh | sh -s -- -b /usr/local/bin
set -euo pipefail
tmp="$(mktemp -d)"
curl -fsSL -o "${tmp}/trufflehog.tar.gz" \
"https://github.com/trufflesecurity/trufflehog/releases/download/v${TRUFFLEHOG_VERSION}/trufflehog_${TRUFFLEHOG_VERSION}_linux_amd64.tar.gz"
echo "${TRUFFLEHOG_SHA256} ${tmp}/trufflehog.tar.gz" | sha256sum --check --strict
tar -xzf "${tmp}/trufflehog.tar.gz" -C "${tmp}" trufflehog
sudo install -m 0755 "${tmp}/trufflehog" /usr/local/bin/trufflehog
rm -rf "${tmp}"
trufflehog --version

- name: Scan for secrets
run: task go:sec:secrets

web-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "20"

- name: Setup Bun
uses: oven-sh/setup-bun@v2
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: "1.3.1"

Expand All @@ -154,9 +147,7 @@ jobs:
bun install --frozen-lockfile

- name: Install Task
run: |
curl -sL https://taskfile.dev/install.sh | sh -s -- -b /usr/local/bin
task --version
uses: ./.github/actions/install-task

- name: Web lint (Typecheck, Biome, and knip)
run: task web:lint
Expand All @@ -170,17 +161,15 @@ jobs:
mock-oauth-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

- name: Setup Bun
uses: oven-sh/setup-bun@v2
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: "1.3.1"

- name: Install Task
run: |
curl -sL https://taskfile.dev/install.sh | sh -s -- -b /usr/local/bin
task --version
uses: ./.github/actions/install-task

- name: Mock OAuth lint (Typecheck and Biome)
run: task mock-oauth:lint
Expand Down
21 changes: 12 additions & 9 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,8 @@ on:
paths: ["docs/**"]
workflow_dispatch:

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write

# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
Expand All @@ -23,32 +20,38 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

- name: Setup Bun
uses: oven-sh/setup-bun@v2
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: "1.3.1"

- name: Install dependencies
run: cd docs && bun install
run: cd docs && bun install --frozen-lockfile

- name: Build docs
run: cd docs && bun run build

- name: Setup Pages
uses: actions/configure-pages@v5
uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0

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

deploy:
needs: build
runs-on: ubuntu-latest
# Only the deploy job may publish to GitHub Pages
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5
Loading
Loading