Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d2c5881
Week 2 Day 1: F-2 v2 byte-identical signup + security-reviewer gate +…
pulkitpareek18 May 18, 2026
c391bb2
iot: R307 fingerprint driver + CLI for the laptop-side test rig
pulkitpareek18 May 18, 2026
0ea4760
iot: retry on TOO_FUZZY / TOO_FEW_FEATURE + log B03 hardware validation
pulkitpareek18 May 18, 2026
6dc69fb
iot: fingerprint demo web app — email + finger login over the bridge
pulkitpareek18 May 18, 2026
fc2b409
iot/demo: streaming progress so the page tells you exactly when to li…
pulkitpareek18 May 18, 2026
c8686ca
iot/demo: auto-retry enrollment on the three "try again" sensor codes
pulkitpareek18 May 18, 2026
c4f96cd
iot/demo: real ZK — Poseidon commitments + Groth16 proofs per signup/…
pulkitpareek18 May 18, 2026
4807e17
iot/demo: per-Pramaan — sensor for capture+match only, host owns ever…
pulkitpareek18 May 18, 2026
aac2c5f
iot/demo: proper auth UI — brand header, tab switcher, email + OTP + …
pulkitpareek18 May 19, 2026
4fe8adc
dashboard: monochrome palette + Fraunces typography + Light/Auto/Dark…
pulkitpareek18 May 19, 2026
8a4e0a4
config: add consoleBaseUrl / docsBaseUrl / landingBaseUrl for the sub…
pulkitpareek18 May 19, 2026
d1d6397
Caddyfile: vhosts for api./console./docs.zeroauth.dev + apex redirects
pulkitpareek18 May 19, 2026
223ba75
console: cross-subdomain cookie + post-verify redirect via consoleBas…
pulkitpareek18 May 19, 2026
85172a8
landing: rewrite internal links to console./docs./api. subdomains
pulkitpareek18 May 19, 2026
a686219
dashboard: docs link reads VITE_DOCS_BASE_URL (defaults to docs.zeroa…
pulkitpareek18 May 19, 2026
78340c5
email-templates: route all links to the new subdomains
pulkitpareek18 May 19, 2026
f576862
docs + README: route every URL to the right subdomain
pulkitpareek18 May 19, 2026
ff3f24d
docs: interactive API playground at /reference/playground
pulkitpareek18 May 19, 2026
7b0713b
tests: realign email assertions to the new subdomain URLs
pulkitpareek18 May 19, 2026
9d207bb
qa-log 2026-05-19-subdomain: capture the api./console./docs. split
pulkitpareek18 May 19, 2026
d4c7dec
tests(dashboard): wrap Login test in ThemeProvider
pulkitpareek18 May 19, 2026
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
25 changes: 18 additions & 7 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,25 @@
NODE_ENV=development
PORT=3000

# Public-facing URL of the API. Used for OIDC issuer, SAML callbacks, console
# quickstart snippets, and CORS origin (unless CORS_ORIGINS overrides).
# Production: https://zeroauth.dev
# Public-facing URLs for the four product surfaces. After the subdomain
# refactor each one resolves to a different vhost in production:
#
# API_BASE_URL → https://api.zeroauth.dev (REST surface)
# CONSOLE_BASE_URL → https://console.zeroauth.dev (React dashboard)
# DOCS_BASE_URL → https://docs.zeroauth.dev (Docusaurus site)
# LANDING_BASE_URL → https://zeroauth.dev (marketing + signup)
#
# In dev they collapse onto a single Express host on :3000 so tests +
# round-trip flows work without DNS plumbing.
API_BASE_URL=http://localhost:3000

# Comma-separated list of allowed CORS origins. Defaults to API_BASE_URL in
# production, or localhost dev origins otherwise.
# Production example: https://zeroauth.dev,https://www.zeroauth.dev
CONSOLE_BASE_URL=http://localhost:3000/dashboard
DOCS_BASE_URL=http://localhost:3000/docs
LANDING_BASE_URL=http://localhost:3000

# Comma-separated list of allowed CORS origins. Defaults derive from the
# four URLs above in production, or localhost variants in dev.
# Production example:
# https://api.zeroauth.dev,https://console.zeroauth.dev,https://docs.zeroauth.dev,https://zeroauth.dev
CORS_ORIGINS=

# Whether to trust X-Forwarded-* headers. Set to true when behind Caddy/Nginx/
Expand Down
111 changes: 111 additions & 0 deletions .github/workflows/security-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
name: Security review gate

# Path-filter gate for the security-sensitive surfaces. Runs on every PR that
# touches auth, crypto, audit, key handling, or tenant-boundary code and
# leaves an annotated comment listing the touched paths + the named subagent
# the human reviewer must invoke locally.
#
# Why this isn't an in-CI security scan: the security-reviewer subagent
# (`.claude/agents/security-reviewer.md`) runs on Opus with full repo context
# and produces a structured findings report. Running that inside GHA would
# require Claude API access from CI, which isn't wired and isn't billed
# centrally. So this workflow is a *forcing function* — it makes skipping
# the manual subagent run impossible to miss in the PR conversation.
#
# Closes the Week 1 discipline gap noted in qa-log/W01-engineering-annex.md.

on:
pull_request:
paths:
- 'src/services/zkp.ts'
- 'src/services/identity.ts'
- 'src/services/api-keys.ts'
- 'src/services/jwt.ts'
- 'src/services/platform.ts'
- 'src/middleware/auth.ts'
- 'src/middleware/tenant-auth.ts'
- 'src/routes/auth.ts'
- 'src/routes/console.ts'
- 'src/routes/v1/**'
- 'src/routes/zkp.ts'
- 'src/routes/saml.ts'
- 'src/routes/oidc.ts'
- 'circuits/**'
- 'contracts/**'
- 'verifier/src/audit-log.ts'
- 'verifier/src/server.ts'

permissions:
contents: read
pull-requests: write

jobs:
flag:
name: Flag for security-reviewer subagent
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Check out
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Collect touched security paths
id: paths
run: |
base="${{ github.event.pull_request.base.sha }}"
head="${{ github.event.pull_request.head.sha }}"
# Recompute the same path set the workflow `paths:` clause matches
# so the comment lists the *exact* files that triggered this run.
touched=$(git diff --name-only "$base" "$head" | grep -E '^(src/services/(zkp|identity|api-keys|jwt|platform)\.ts|src/middleware/(auth|tenant-auth)\.ts|src/routes/(auth|console|zkp|saml|oidc)\.ts|src/routes/v1/.+|circuits/.+|contracts/.+|verifier/src/(audit-log|server)\.ts)$' || true)
# Newline-escape for GHA multi-line output
{
echo "touched<<EOF"
echo "$touched"
echo "EOF"
} >> "$GITHUB_OUTPUT"

- name: Annotate PR with subagent invocation reminder
uses: actions/github-script@v8
with:
script: |
const touched = `${{ steps.paths.outputs.touched }}`.trim();
if (!touched) {
core.info('No security-sensitive paths touched; nothing to flag.');
return;
}
const list = touched.split('\n').map(p => `- \`${p}\``).join('\n');
const marker = '<!-- security-review-gate -->';
const body = `${marker}

## 🔒 Security review required

This PR touches security-sensitive surfaces. Per [CLAUDE.md §4](../blob/main/CLAUDE.md#standing-instructions), the \`security-reviewer\` subagent ([\`.claude/agents/security-reviewer.md\`](../blob/main/.claude/agents/security-reviewer.md)) must be invoked locally before merge.

**Touched paths:**
${list}

**How to run the review:**
\`\`\`
# In Claude Code, after pulling this branch:
@security-reviewer review the changes on this branch
\`\`\`

Reply on this PR with the structured findings report (or a "no findings" confirmation) before requesting merge. Block merge if any Critical / High finding lands without a tracked carve-out.

_This comment is posted automatically by \`.github/workflows/security-review.yml\` and updated on every push to keep the touched-paths list current._`;

const { owner, repo } = context.repo;
const prNumber = context.payload.pull_request.number;
const existing = await github.paginate(
github.rest.issues.listComments,
{ owner, repo, issue_number: prNumber }
);
const prior = existing.find(c => c.body && c.body.startsWith(marker));
if (prior) {
await github.rest.issues.updateComment({ owner, repo, comment_id: prior.id, body });
core.info(`Updated existing security-review comment ${prior.id}`);
} else {
await github.rest.issues.createComment({ owner, repo, issue_number: prNumber, body });
core.info('Posted new security-review comment');
}
113 changes: 113 additions & 0 deletions .github/workflows/verifier-chain-verify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
name: Verifier audit-chain verify

# Daily integrity check on the verifier's append-only SQLite audit log.
# Reaches into the VPS over SSH (verifier is loopback-only on :3001 by
# design) and calls /audit/verify-chain. If `ok:false`, opens an issue
# with the `incident:critical` label and pings via the audit-chain
# breakage runbook in governance: docs/shared/incident-response.md.
#
# Cadence: daily at 02:30 UTC (08:00 IST), and on demand via workflow_dispatch.
# A failure here means the hash chain in `verifier_events` is broken — almost
# always a sign of tampering, never of normal operation. See A-V01 in
# governance: docs/threat-model/verifier.md.

on:
schedule:
- cron: '30 2 * * *'
workflow_dispatch:

env:
DEPLOY_HOST: 104.207.143.14
DEPLOY_USER: zeroauth-deploy

permissions:
contents: read
issues: write

jobs:
verify-chain:
name: Probe /audit/verify-chain
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Start SSH agent
uses: webfactory/ssh-agent@v0.10.0
with:
ssh-private-key: ${{ secrets.DEPLOY_SSH_KEY }}

- name: Add deploy host to known_hosts
run: ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts

- name: Probe verifier audit chain
id: probe
run: |
# Verifier is on the docker compose network, loopback-bound. We
# exec into the running container's curl rather than punching a
# port through to the host — keeps the loopback invariant intact.
response=$(ssh "$DEPLOY_USER@$DEPLOY_HOST" \
"docker exec zeroauth-verifier wget -qO- http://127.0.0.1:3001/audit/verify-chain" || true)

echo "raw_response=$response"
# Normalize newlines for the multi-line GHA output
{
echo "response<<EOF"
echo "$response"
echo "EOF"
} >> "$GITHUB_OUTPUT"

# Look for `"ok":true` in the JSON body. If the verifier is down
# or returns a non-2xx, $response will be empty and `ok:true`
# won't match — caught below.
if echo "$response" | grep -q '"ok":true'; then
echo "status=green" >> "$GITHUB_OUTPUT"
echo "Chain intact."
else
echo "status=red" >> "$GITHUB_OUTPUT"
echo "Chain probe failed or returned ok:false. Response: $response"
exit 1
fi

- name: Open critical issue on failure
if: failure()
uses: actions/github-script@v8
with:
script: |
const date = new Date().toISOString().slice(0, 10);
const response = `${{ steps.probe.outputs.response }}`.slice(0, 4000);
const title = `Verifier audit-chain probe failed — ${date}`;
const body = `The daily \`/audit/verify-chain\` probe came back non-green.

**Run:** ${context.payload.repository.html_url}/actions/runs/${context.runId}

**Probe response:**
\`\`\`json
${response || '(empty — verifier likely unreachable)'}
\`\`\`

**What to do:**
1. Verify the chain integrity manually: \`ssh zeroauth-deploy@${process.env.DEPLOY_HOST} 'docker exec zeroauth-verifier wget -qO- http://127.0.0.1:3001/audit/verify-chain'\`
2. If \`ok:false\`, treat as a Security incident (A-V01 in [governance: docs/threat-model/verifier.md](https://github.com/zeroauth-dev/ZeroAuth-Governance/blob/main/docs/threat-model/verifier.md)). Run the [incident-response runbook](https://github.com/zeroauth-dev/ZeroAuth-Governance/blob/main/docs/shared/incident-response.md).
3. If the verifier is unreachable, restart with \`docker compose --profile prod up -d --force-recreate zeroauth-verifier\` and re-run this workflow.

🤖 Filed automatically by \`.github/workflows/verifier-chain-verify.yml\`.`;

const { owner, repo } = context.repo;
// Look for an existing open issue from a prior failure today so
// we don't spam if the verifier is down for hours.
const existing = await github.paginate(
github.rest.issues.listForRepo,
{ owner, repo, state: 'open', labels: 'incident:critical', per_page: 50 }
);
const today = existing.find(i => i.title && i.title.includes(date));
if (today) {
core.info(`Existing issue ${today.number} already covers today's probe failure.`);
return;
}
const created = await github.rest.issues.create({
owner,
repo,
title,
body,
labels: ['incident:critical', 'verifier', 'audit-log'],
});
core.info(`Opened issue #${created.data.number}`);
Loading
Loading