diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index fdf7ddd..0112a32 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -6,7 +6,7 @@ body:
attributes:
value: |
Thanks for taking the time to file a bug report. **Security
- vulnerabilities should not be reported here** — see [SECURITY.md](https://github.com/pulkitpareek18/ZeroAuth/blob/main/SECURITY.md).
+ vulnerabilities should not be reported here** — see [SECURITY.md](https://github.com/zeroauth-dev/ZeroAuth/blob/main/SECURITY.md).
- type: textarea
id: description
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index c968ced..598102a 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,11 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: Security vulnerability
- url: https://github.com/pulkitpareek18/ZeroAuth/blob/main/SECURITY.md
+ url: https://github.com/zeroauth-dev/ZeroAuth/blob/main/SECURITY.md
about: Please report security issues privately, not as a public issue.
- name: Documentation
url: https://zeroauth.dev/docs/
about: Browse the full hosted documentation site.
- name: Discussions
- url: https://github.com/pulkitpareek18/ZeroAuth/discussions
+ url: https://github.com/zeroauth-dev/ZeroAuth/discussions
about: Ask questions or propose changes before opening an issue.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
index 8b597e5..f2eb7b3 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.yml
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -6,7 +6,7 @@ body:
attributes:
value: |
For broader proposals, consider opening a
- [discussion](https://github.com/pulkitpareek18/ZeroAuth/discussions)
+ [discussion](https://github.com/zeroauth-dev/ZeroAuth/discussions)
first so we can align on scope.
- type: textarea
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 52ae99c..ee44140 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -5,11 +5,11 @@ welcome bug reports, fixes, integrations, and ideas.
## Ways to contribute
-- **Found a bug?** Open an [issue](https://github.com/pulkitpareek18/ZeroAuth/issues/new).
+- **Found a bug?** Open an [issue](https://github.com/zeroauth-dev/ZeroAuth/issues/new).
Include reproduction steps, expected vs. actual behaviour, and your
environment (`node -v`, OS, Docker version).
- **Have a feature in mind?** Start a
- [discussion](https://github.com/pulkitpareek18/ZeroAuth/discussions) before
+ [discussion](https://github.com/zeroauth-dev/ZeroAuth/discussions) before
opening a PR — we'd rather agree on direction first.
- **Found a security vulnerability?** **Do not** open a public issue. See
[SECURITY.md](SECURITY.md).
@@ -17,7 +17,7 @@ welcome bug reports, fixes, integrations, and ideas.
## Development setup
```bash
-git clone https://github.com/pulkitpareek18/ZeroAuth.git
+git clone https://github.com/zeroauth-dev/ZeroAuth.git
cd ZeroAuth
npm run setup # installs all workspaces, builds everything
cp .env.example .env # local env (uses Base Sepolia testnet by default)
diff --git a/README.md b/README.md
index 1c10b1e..533139e 100644
--- a/README.md
+++ b/README.md
@@ -22,9 +22,9 @@
-
-
-
+
+
+
@@ -125,7 +125,7 @@ Full API reference at [zeroauth.dev/docs/reference/api-reference](https://zeroau
### Run it yourself (Docker, ~2 minutes)
```bash
-git clone https://github.com/pulkitpareek18/ZeroAuth.git
+git clone https://github.com/zeroauth-dev/ZeroAuth.git
cd ZeroAuth
cp .env.example .env # generates fresh secrets via scripts/deploy.sh
./scripts/deploy.sh dev
diff --git a/adr/0004-governance-in-separate-repo.md b/adr/0004-governance-in-separate-repo.md
index 1d35b63..e751228 100644
--- a/adr/0004-governance-in-separate-repo.md
+++ b/adr/0004-governance-in-separate-repo.md
@@ -1,4 +1,4 @@
-# ADR-0004 — Split governance docs into a separate repo (`pulkitpareek18/ZeroAuth-Governance`)
+# ADR-0004 — Split governance docs into a separate repo (`zeroauth-dev/ZeroAuth-Governance`)
## Status
@@ -23,12 +23,12 @@ The reasons we revisited:
1. **The DPDP §8(7) breach-notification procedure was unwritten.** No document anywhere named which lawyer gets called, in what time window, with what information. That's a legal-teeth gap, not a hygiene gap. It has to land somewhere; writing it in a code repo would mix legal blast radius with engineering blast radius.
2. **Compliance mappings have multiple-regulator scope.** A DPDP / IRDAI / RBI / MeitY mapping is read by auditors and a buyer's security team. Forcing them to clone a TypeScript repo to find it is friction at exactly the wrong moment in a pilot conversation.
-3. **The canonical threat model needs a stable URL** before repo #2 (verifier, B02, Week 2) exists. If the verifier's component threat model points at `pulkitpareek18/ZeroAuth/docs/threat_model.md`, the link rots the moment we split the verifier; if it points at a governance repo, the URL is stable forever.
+3. **The canonical threat model needs a stable URL** before repo #2 (verifier, B02, Week 2) exists. If the verifier's component threat model points at `zeroauth-dev/ZeroAuth/docs/threat_model.md`, the link rots the moment we split the verifier; if it points at a governance repo, the URL is stable forever.
4. **Two-reviewer enforcement is easier with a dedicated repo.** Path-globbed CODEOWNERS in a code repo gets bypassed under deadline pressure ("just merge the policy change inline, fix it later"). A standalone repo where every PR is *by definition* a policy change makes the discipline mechanical.
## Decision
-Create `pulkitpareek18/ZeroAuth-Governance` as a separate public GitHub repo with the structure from `governance_CLAUDE.md`:
+Create `zeroauth-dev/ZeroAuth-Governance` as a separate public GitHub repo with the structure from `governance_CLAUDE.md`:
- `docs/shared/{security-policy, coding-standards, naming-conventions, incident-response, breach-notification}.md`
- `docs/threat-model/{canonical, api, verifier, iot, sdk, dashboard}.md`
@@ -41,7 +41,7 @@ Create `pulkitpareek18/ZeroAuth-Governance` as a separate public GitHub repo wit
The repo is **public**, CC-BY-4.0 licensed — same posture as the main `ZeroAuth` repo. The audit story benefits from open visibility.
-This repo (`pulkitpareek18/ZeroAuth`) keeps:
+This repo (`zeroauth-dev/ZeroAuth`) keeps:
- `CLAUDE.md` — the constitution for this repo, links to the canonical shared docs
- `docs/api_contract.md` — API-specific contract (won't move)
@@ -53,7 +53,7 @@ This repo (`pulkitpareek18/ZeroAuth`) keeps:
- **Positive — DPDP §8(7) procedure now exists.** Written down, with named counsel contacts (TODO entries where contacts aren't confirmed yet). Drillable. Reviewable.
- **Positive — auditor-friendly surface.** A buyer's security team can clone one repo and read every policy without slogging through TypeScript. The W08 evidence-pack assembler from the operational suite reads from `evidence-pack-sources/CHECKSUMS.md` cleanly.
-- **Positive — stable URLs across the 8-week build.** When B02 (verifier, Week 2), B03 (IoT, Week 3), B04 (SDK, Week 5) split out, they all link to `github.com/pulkitpareek18/ZeroAuth-Governance/blob/main/docs/threat-model/canonical.md` — that URL doesn't move.
+- **Positive — stable URLs across the 8-week build.** When B02 (verifier, Week 2), B03 (IoT, Week 3), B04 (SDK, Week 5) split out, they all link to `github.com/zeroauth-dev/ZeroAuth-Governance/blob/main/docs/threat-model/canonical.md` — that URL doesn't move.
- **Positive — two-reviewer rule is mechanical.** CODEOWNERS in the governance repo names both Pulkit and Amit on `/docs/shared/` and `/docs/compliance/`. Counsel review is enforced manually (counsel doesn't have GitHub access) by a note in the PR description before merge.
- **Negative — two repos to clone on a fresh dev machine.** Mitigated: `scripts/setup-dev.sh` (TODO) will clone both side by side.
- **Negative — cross-repo links rot more easily than same-repo links.** Mitigated by `markdown-link-check` CI on every PR in both repos.
@@ -81,8 +81,8 @@ The governance repo doesn't get folded back into the API repo. The split is mono
- B06 build prompt: `zeroauth_prompt_suite/04_development_suite/02_claude_code_dev/build_prompts/B06_governance_repo_bootstrap.md`
- Governance constitution: `zeroauth_prompt_suite/04_development_suite/02_claude_code_dev/CLAUDE_md/governance_CLAUDE.md`
-- New repo:
-- Canonical threat model (new home):
+- New repo:
+- Canonical threat model (new home):
- Brainstorm session on Day 3 (Wed May 13 2026) weighing collapsed vs separate repo: this conversation
---
diff --git a/adr/0005-adopt-nodemailer-for-smtp.md b/adr/0005-adopt-nodemailer-for-smtp.md
index 002264e..0339940 100644
--- a/adr/0005-adopt-nodemailer-for-smtp.md
+++ b/adr/0005-adopt-nodemailer-for-smtp.md
@@ -6,9 +6,9 @@ Accepted
## Context
-[Issue #27](https://github.com/pulkitpareek18/ZeroAuth/issues/27) (F-2 from PR #22 security review) needs email infrastructure to close the email-enumeration finding properly. Beyond that single fix, several pending workstreams converge on "we need transactional email":
+[Issue #27](https://github.com/zeroauth-dev/ZeroAuth/issues/27) (F-2 from PR #22 security review) needs email infrastructure to close the email-enumeration finding properly. Beyond that single fix, several pending workstreams converge on "we need transactional email":
-- **Breach-notification procedure** in `pulkitpareek18/ZeroAuth-Governance: docs/shared/breach-notification.md` step §3 requires emailing every affected tenant within 6 hours of confirmation — currently has no implementation
+- **Breach-notification procedure** in `zeroauth-dev/ZeroAuth-Governance: docs/shared/breach-notification.md` step §3 requires emailing every affected tenant within 6 hours of confirmation — currently has no implementation
- **Password reset flow** — entirely missing today; we ship console accounts with no recovery path
- **Welcome email on signup** — minor UX win, plus a server-side signal that the address is real
- **"Someone tried to sign up with your email" notice** — security signal for legitimate account holders, partial mitigation for F-2 enumeration
@@ -57,7 +57,7 @@ Adopt **`nodemailer` v8.x** (latest stable, MIT-0 licensed) as the SMTP transpor
## Threat model delta
-- New egress to `smtp-relay.brevo.com:587` from the API process. Update `pulkitpareek18/ZeroAuth-Governance: docs/threat-model/canonical.md` to add A-V06 (SMTP credential exfiltration / Brevo account takeover risk) — tracked as a follow-up.
+- New egress to `smtp-relay.brevo.com:587` from the API process. Update `zeroauth-dev/ZeroAuth-Governance: docs/threat-model/canonical.md` to add A-V06 (SMTP credential exfiltration / Brevo account takeover risk) — tracked as a follow-up.
## Operational notes
@@ -71,8 +71,8 @@ Adopt **`nodemailer` v8.x** (latest stable, MIT-0 licensed) as the SMTP transpor
- nodemailer source:
- nodemailer license (MIT-0):
- Brevo SMTP docs:
-- DPDP §8(7) breach-notification procedure that depends on this: `pulkitpareek18/ZeroAuth-Governance: docs/shared/breach-notification.md`
-- Issue this unblocks:
+- DPDP §8(7) breach-notification procedure that depends on this: `zeroauth-dev/ZeroAuth-Governance: docs/shared/breach-notification.md`
+- Issue this unblocks:
---
diff --git a/adr/0006-verifier-typescript-not-rust.md b/adr/0006-verifier-typescript-not-rust.md
index d916fad..2da7631 100644
--- a/adr/0006-verifier-typescript-not-rust.md
+++ b/adr/0006-verifier-typescript-not-rust.md
@@ -20,13 +20,13 @@ The plan-mode doc's §3.3 recommended **Plan A**. Pulkit picked **Plan B** on Th
## Decision
-The Groth16 verifier ships as **`@zeroauth/verifier`, an npm workspace inside `pulkitpareek18/ZeroAuth`**, written in TypeScript on top of `snarkjs`. It runs as a separate Docker container (`zeroauth-verifier`) bound to `127.0.0.1:3001` on the Docker network. The API container reaches it via HTTP — never inline anymore.
+The Groth16 verifier ships as **`@zeroauth/verifier`, an npm workspace inside `zeroauth-dev/ZeroAuth`**, written in TypeScript on top of `snarkjs`. It runs as a separate Docker container (`zeroauth-verifier`) bound to `127.0.0.1:3001` on the Docker network. The API container reaches it via HTTP — never inline anymore.
Shipped in three PRs today:
-- [PR #35](https://github.com/pulkitpareek18/ZeroAuth/pull/35) — Dockerfile `verifier-build` + `verifier-production` stages, compose service, `VERIFIER_URL` wired into the API's environment.
-- [PR #36](https://github.com/pulkitpareek18/ZeroAuth/pull/36) — Healthcheck hotfix (`localhost` → `127.0.0.1` because alpine busybox `wget` hits IPv6 first).
-- [PR #37](https://github.com/pulkitpareek18/ZeroAuth/pull/37) — SQLite append-only audit log + hash chain (the design doc §4.3 component).
+- [PR #35](https://github.com/zeroauth-dev/ZeroAuth/pull/35) — Dockerfile `verifier-build` + `verifier-production` stages, compose service, `VERIFIER_URL` wired into the API's environment.
+- [PR #36](https://github.com/zeroauth-dev/ZeroAuth/pull/36) — Healthcheck hotfix (`localhost` → `127.0.0.1` because alpine busybox `wget` hits IPv6 first).
+- [PR #37](https://github.com/zeroauth-dev/ZeroAuth/pull/37) — SQLite append-only audit log + hash chain (the design doc §4.3 component).
The inline-`snarkjs` fallback in `src/services/zkp.ts` **stays in the codebase for two more weeks** as a safety net while the verifier service soaks in production. It activates only when `VERIFIER_URL` is unset (which never happens in prod — the value is hard-set in `docker-compose.yml`'s `environment:` block). Retirement is scheduled for end of Week 4 of the build cycle (~2026-06-08), as a separate PR.
@@ -52,7 +52,7 @@ Single-engineer velocity. The Rust path was the brainstorm's recommendation when
- **No reproducible build provenance** for the verifier image. Docker `buildx --provenance --sbom` would produce signed attestations, but the `better-sqlite3` native build (alpine arm64-musl has no prebuilt → node-gyp compile via apk-added python+make+g++) is non-deterministic. The audit story is therefore "trust the image" not "verify the image's bytes." Acceptable for v0; this is the single biggest delta vs Plan A.
- **Larger transitive surface.** snarkjs has ~12 transitive deps vs arkworks' ~6. Each is JS, MIT-licensed, audited; but the larger surface is real.
- **`cryptographer-reviewer` subagent calibration** assumes Rust + arkworks per its current spec. The subagent works against snarkjs too (it's just JS) but the review is less precise — Rust's type system catches a class of memory-safety bugs the reviewer can stop looking for. With snarkjs, the reviewer has to reason about JS-level invariants. Documented in the subagent's known-limitations section (TBD).
-- **No `--unsafe` audit story.** TypeScript has no equivalent of Rust's `unsafe` block, so the "no unsafe without an ADR" rule in B02's quality bar doesn't transfer. The closest analog is "no `any` in exported signatures + no `dangerouslySetInnerHTML` in user-rendering code" which is already in our [`coding-standards.md`](https://github.com/pulkitpareek18/ZeroAuth-Governance/blob/main/docs/shared/coding-standards.md).
+- **No `--unsafe` audit story.** TypeScript has no equivalent of Rust's `unsafe` block, so the "no unsafe without an ADR" rule in B02's quality bar doesn't transfer. The closest analog is "no `any` in exported signatures + no `dangerouslySetInnerHTML` in user-rendering code" which is already in our [`coding-standards.md`](https://github.com/zeroauth-dev/ZeroAuth-Governance/blob/main/docs/shared/coding-standards.md).
- **Container image size is bigger.** Alpine + node + snarkjs + better-sqlite3 → ~140MB. A static Rust binary would be ~20MB. We're not bandwidth-constrained at single-VPS scale; revisit if/when we go multi-region.
### Neutral
@@ -85,8 +85,8 @@ If during the soak window any verifier failure mode surfaces that we can't fix f
- Plan-mode design doc: [`docs/design/verifier-service-split.md`](../docs/design/verifier-service-split.md)
- B02 build prompt (rejected path): `zeroauth_prompt_suite/04_development_suite/02_claude_code_dev/build_prompts/B02_verifier_service_bootstrap.md`
-- Issue tracking: [#35](https://github.com/pulkitpareek18/ZeroAuth/pull/35), [#36](https://github.com/pulkitpareek18/ZeroAuth/pull/36), [#37](https://github.com/pulkitpareek18/ZeroAuth/pull/37)
-- Component threat model (to be promoted from stub in the governance repo): `pulkitpareek18/ZeroAuth-Governance: docs/threat-model/verifier.md`
+- Issue tracking: [#35](https://github.com/zeroauth-dev/ZeroAuth/pull/35), [#36](https://github.com/zeroauth-dev/ZeroAuth/pull/36), [#37](https://github.com/zeroauth-dev/ZeroAuth/pull/37)
+- Component threat model (to be promoted from stub in the governance repo): `zeroauth-dev/ZeroAuth-Governance: docs/threat-model/verifier.md`
---
diff --git a/docs/design/verifier-service-split.md b/docs/design/verifier-service-split.md
index c35cb94..24ee481 100644
--- a/docs/design/verifier-service-split.md
+++ b/docs/design/verifier-service-split.md
@@ -60,7 +60,7 @@ This is the decision Pulkit needs to make before Thursday morning. I lay out bot
### 3.1 Plan A — full B02 (Rust verifier in its own repo)
-**Repo:** new `pulkitpareek18/ZeroAuth-Verifier` (public, MIT, Rust).
+**Repo:** new `zeroauth-dev/ZeroAuth-Verifier` (public, MIT, Rust).
**What gets built:**
@@ -99,7 +99,7 @@ This is the decision Pulkit needs to make before Thursday morning. I lay out bot
### 3.2 Plan B — TypeScript split into a sub-workspace (the pragmatic shortcut)
-**Repo:** stays in `pulkitpareek18/ZeroAuth`. New directory `verifier/` becomes a separate npm workspace.
+**Repo:** stays in `zeroauth-dev/ZeroAuth`. New directory `verifier/` becomes a separate npm workspace.
**What gets built:**
@@ -132,7 +132,7 @@ I need one of:
- **A.** "Go Plan A (Rust separate repo)." → tomorrow I scaffold the Rust crate.
- **B.** "Go Plan B (TypeScript workspace)." → tomorrow I peel the Node code into `verifier/`.
-- **C.** "Hold — start B02 next week as the brainstorm says, do something else Thursday." → I roll Thursday into closing PR #22's three Mediums (issue [#26](https://github.com/pulkitpareek18/ZeroAuth/issues/26)) and the W05 review prep.
+- **C.** "Hold — start B02 next week as the brainstorm says, do something else Thursday." → I roll Thursday into closing PR #22's three Mediums (issue [#26](https://github.com/zeroauth-dev/ZeroAuth/issues/26)) and the W05 review prep.
If no decision by EOD Wednesday, default = C (defer).
@@ -307,7 +307,7 @@ Reproducibility check (the `.github/workflows/reproducible-build.yml`): build tw
### 4.7 API repo changes
-Inside `pulkitpareek18/ZeroAuth`:
+Inside `zeroauth-dev/ZeroAuth`:
1. **`src/services/zkp.ts` shrinks** to ~40 lines. New surface:
@@ -339,7 +339,7 @@ Inside `pulkitpareek18/ZeroAuth`:
**Thursday Day 4 — scaffold + verifier service**
-1. Morning: `gh repo create pulkitpareek18/ZeroAuth-Verifier --public`. Clone locally. Add `CLAUDE.md` (copying conventions from API repo).
+1. Morning: `gh repo create zeroauth-dev/ZeroAuth-Verifier --public`. Clone locally. Add `CLAUDE.md` (copying conventions from API repo).
2. `cargo init --bin verifier-service && cargo new --lib verifier-core` workspace setup.
3. Implement `verifier-core` with arkworks Groth16. Write unit + property tests **first**.
4. Implement `verifier-service` HTTP shell with axum. Single `/verify` route, no audit log yet.
@@ -368,12 +368,12 @@ Inside `pulkitpareek18/ZeroAuth`:
| Append-only | `zeroauth-verifier: tests/audit_append_only.rs` | `UPDATE verifier_events …` → SQL trigger aborts; same for DELETE |
| Hash chain | `zeroauth-verifier: tests/hash_chain.rs` | After N writes, `verify_chain.rs` reconstructs every `entry_hash` from `prev_hash || canonical(row)`. Mutating any column breaks the chain. |
| Reproducible build | `.github/workflows/reproducible-build.yml` | Two clean builds produce identical OCI digest |
-| API repo regression | `pulkitpareek18/ZeroAuth: tests/zkp.test.ts` | After the split, every existing test stays green |
-| End-to-end | `pulkitpareek18/ZeroAuth: dashboard/e2e/happy-path.spec.ts` | Signup → first key → verification call → audit log entry — all still works |
+| API repo regression | `zeroauth-dev/ZeroAuth: tests/zkp.test.ts` | After the split, every existing test stays green |
+| End-to-end | `zeroauth-dev/ZeroAuth: dashboard/e2e/happy-path.spec.ts` | Signup → first key → verification call → audit log entry — all still works |
### 4.10 Threat model deltas
-After the split, update `pulkitpareek18/ZeroAuth-Governance: docs/threat-model/`:
+After the split, update `zeroauth-dev/ZeroAuth-Governance: docs/threat-model/`:
- **`canonical.md`** — A-02 (replayed proof verification) — mitigation summary updates: "issued-nonce binding lives in the verifier service, not the API"
- **`api.md`** — A-02 section pointer changes from "primary mitigation lives in API" to "delegated to verifier"
@@ -429,7 +429,7 @@ Explicitly NOT in this design:
1. Plan A vs B vs C — pick one.
2. (If A:) Rust toolchain ready on dev machine? `rustc --version` ≥ 1.85.
3. (If A:) Confirmation that the existing `circuits/identity_proof.vkey.json` is BN254-compatible — I'll verify the JSON shape Thursday morning, but if you already know, save me the half-hour.
-4. (If A:) Permission to create `pulkitpareek18/ZeroAuth-Verifier` as a public repo.
+4. (If A:) Permission to create `zeroauth-dev/ZeroAuth-Verifier` as a public repo.
5. Acknowledgement that this work spans Thu + Fri and may bleed into Monday Week 2. The other Day 4/5 items (closing PR #22's Mediums) get re-prioritized.
If no answer by EOD Wednesday: default = **C (defer to Week 2 Day 1, do PR #22 Mediums Thursday/Friday)**.
diff --git a/docs/threat_model.md b/docs/threat_model.md
index 668477c..9c5471b 100644
--- a/docs/threat_model.md
+++ b/docs/threat_model.md
@@ -102,7 +102,7 @@
|---|---|
| **Class** | Information disclosure / EoP (STRIDE: I + E) |
| **Surface** | Anything rendered inside the dashboard SPA at `/dashboard/*` |
-| **Description** | The console JWT is **persisted to `localStorage`** under the key `zeroauth.console_token` by `dashboard/src/lib/api.ts` so the session survives page reloads. If an XSS payload executes in the SPA, the attacker reads the token from `localStorage` and uses it for the remaining lifetime of the token (≤ 24h). This is a deliberate trade-off vs. in-memory storage (better UX, worse blast radius) — captured here so the threat model is honest about the choice. See [`pulkitpareek18/ZeroAuth-Governance: docs/threat-model/dashboard.md` §A-09](https://github.com/pulkitpareek18/ZeroAuth-Governance/blob/main/docs/threat-model/dashboard.md) for the authoritative component-level write-up. |
+| **Description** | The console JWT is **persisted to `localStorage`** under the key `zeroauth.console_token` by `dashboard/src/lib/api.ts` so the session survives page reloads. If an XSS payload executes in the SPA, the attacker reads the token from `localStorage` and uses it for the remaining lifetime of the token (≤ 24h). This is a deliberate trade-off vs. in-memory storage (better UX, worse blast radius) — captured here so the threat model is honest about the choice. See [`zeroauth-dev/ZeroAuth-Governance: docs/threat-model/dashboard.md` §A-09](https://github.com/zeroauth-dev/ZeroAuth-Governance/blob/main/docs/threat-model/dashboard.md) for the authoritative component-level write-up. |
| **Mitigation** | (a) Strict CSP from Helmet — no `unsafe-eval`, no inline scripts beyond the existing landing-page allowance. (b) React's default escape protects against most reflected XSS. (c) **Never** introduce `dangerouslySetInnerHTML` without an ADR — enforced by reviewer rule. (d) The console JWT is short-lived (24h) and now carries `jti` + `aud='zeroauth-console'` (issue #26 F-5, commit landed Day 3 Week 1) — `jti` is the seam for a future Redis-backed allow-list that makes "logout everywhere" possible. (e) Console JWT is rejected on any `/v1` endpoint because `aud` is verified explicitly. |
| **Test status** | CSP header presence is asserted in `tests/health.test.ts` (indirectly via helmet output). **Missing:** an integration test that asserts no inline `