# Auth + Privacy Foundation

Implemented in `feature/auth-privacy-foundation` (v2.1.0, 2026-05-17). Adds pluggable authentication, multi-tenant org model, artifact ACLs, at-rest encryption, per-artifact passphrase protection, and a compliance-grade audit trail.

## New commands

```bash
npm run check:disk-enc   # boot-time disk-encryption health check; writes audit_logs row
```

## New library files

| File | Purpose |
|------|---------|
| `lib/auth.js` | Auth adapter. `getSession(req)`, `requireSession(req)`, `getLocalDevUser()`. Branches on `MADARX_AUTH_MODE`. |
| `lib/authz.js` | Authorization layer. `canRead`, `canWrite`, `canShare`, `canDelete`, `requireRole`, `assertArtifactAccess`. Hybrid membership cache: 5-minute TTL on read paths; sensitive paths (sharing, downloads, admin, `irreversible_external`) call Supabase live (RD-1). |
| `lib/org.js` | Org domain: `createOrg`, `getOrg`, `listOrgs`, `addMember`, `removeMember`, `getMembership`, `listMembers`. |
| `lib/artifact-acl.js` | Artifact ACL: `getAcl`, `setAcl`, `createShareLink`, `verifyShareLink`, `revokeShareLink`. |
| `lib/artifact-crypto.js` | Per-org DEK derivation and artifact encrypt/decrypt. `encryptArtifact`, `decryptArtifact`, `deriveOrgDek`. AES-256-GCM, 12-byte random IV, 16-byte auth tag. Disk format: `[IV][tag][ciphertext]`. DEK never stored (RD-2). |
| `lib/compliance-audit.js` | High-integrity audit writer. `appendComplianceAudit(entry)` — INSERT only, never UPDATE/DELETE. |
| `lib/privacy-defaults.js` | Default KDF parameters for the privacy module (iterations, kdf, idle timeout). |
| `scripts/migrate-auth-v1.mjs` | One-shot idempotent migration: seeds default org, assigns `personal` project to it, creates synthetic owner user. Runs automatically on first boot via `lib/seeds.js`. |
| `scripts/check-disk-encryption.mjs` | Detects FileVault / LUKS / BitLocker; writes `disk_encryption.health` audit row; emits stderr warning in production when off. |

## New tables (all in `lib/schema.js`)

| Table | Purpose |
|-------|---------|
| `privacy_config` | Per-device passphrase derivation parameters: `kdf`, `iterations`, `salt_b64`, `hmac_b64`, `idle_timeout_ms`. One active row. |
| `organizations` | Root tenant scope. `id`, `slug`, `name`, `plan`, `mfa_required`, `ip_allowlist_json`, `session_timeout_seconds`. |
| `users` | Local mirror of Supabase Auth users. In `local` mode holds one synthetic row. `id`, `email`, `display_name`, `avatar_url`, `auth_provider`, `mfa_enrolled`, `last_sign_in_at`. |
| `org_memberships` | Links users to orgs with roles: `owner` / `admin` / `member` / `viewer`. Unique on `(org_id, user_id)`. |
| `teams` | Phase 1 stub. `id`, `org_id`, `name`. Not populated until team-sharing lands. |
| `team_memberships` | Phase 1 stub. `(team_id, user_id, role)` composite PK. |
| `artifact_acls` | One row per artifact. `scope`: `private` / `team:<team_id>` / `org` / `link`. |
| `artifact_share_links` | Signed share link registry. `token` (HMAC-SHA256 with `MADARX_SHARE_LINK_SECRET`), `expires_at`, `max_accesses`, `access_count`, `revoked`. |
| `artifact_share_link_otps` | Email-OTP codes for share-link access. Short-lived; deleted after successful verification. |

## New columns on existing tables (all via `ALTER TABLE` in `lib/db.js`)

| Table | New columns |
|-------|-------------|
| `artifacts` | `is_protected` (INT, default 0), `owner_user_id`, `sharing_scope` (default `private`), `encrypted` (INT, default 0), `encryption_key_id` |
| `projects` | `org_id` |
| `audit_logs` | `user_id`, `org_id`, `ip`, `user_agent`, `action_target_id`, `action_target_type` |
| `activity_log` | `user_id`, `org_id`, `ip` |
| `heartbeat_tasks` | `acting_on_behalf_of_user_id` |
| `schedules` | `created_by_user_id` |

## Append-only audit triggers

Two `BEFORE UPDATE` and `BEFORE DELETE` triggers on `audit_logs` raise a SQLite error at the database layer. Added in `lib/db.js` right after the schema DDL runs, guarded by `IF NOT EXISTS`. `lib/compliance-audit.js` is the canonical writer; `appendAudit()` in `lib/runtime.js` only INSERTs and is unaffected.

## Gate 0 identity gate

`lib/tools/registry.js::executeRegistryTool()` accepts `user_id` in the call context and includes it in every `approval_requests` and `audit_logs` row it writes. This ensures the audit trail always has a human identity attached to machine actions. Authorization enforcement is handled by `lib/authz.js`.

## Service-principal propagation

`schedules.created_by_user_id` is copied into heartbeat task `acting_on_behalf_of_user_id` by the cron daemon, then threaded through `lib/agent-runner.js::runAgentTick()` to every `audit_logs` row the run writes.

## API routes

See [api-surface.md](api-surface.md) for the full route list (auth, orgs, artifact ACL, share links, admin, privacy, extended audit search).

## New env vars

| Variable | Default | Description |
|----------|---------|-------------|
| `MADARX_AUTH_MODE` | `local` | `local` (dev pass-through) or `supabase` (production). `local` in `NODE_ENV=production` emits a console warning. |
| `MADARX_SHARE_LINK_SECRET` | — | 32+ random bytes for HMAC-signing share link tokens. Required when sharing is used. Rotation invalidates existing links. |
| `MADARX_ARTIFACT_ENCRYPTION` | `0` | Set to `1` to enable AES-256-GCM encryption on new artifact disk writes. Existing plaintext artifacts remain readable. |
| `MADARX_MEMBERSHIP_LIVE_CHECK` | `on` (supabase mode) | Live Supabase membership check on sensitive paths. Do not set to `off` in production. |
| `MADARX_OWNER_EMAIL` | — | Email address for the local-mode synthetic owner user. |
| `NEXT_PUBLIC_SUPABASE_URL` | — | Supabase project URL (supabase mode only). |
| `NEXT_PUBLIC_SUPABASE_ANON_KEY` | — | Supabase anon key (supabase mode, browser-visible). |
| `SUPABASE_SERVICE_ROLE_KEY` | — | Supabase service role key (supabase mode, server-only — store in secrets vault). |

## Privacy module binding decisions

| Decision | Rationale |
|----------|-----------|
| PBKDF2 derivation in Web Worker | RD-1 — prevents UI freeze on 600,000-iteration derivation |
| 15-minute fixed lockout timeout | RD-2 — idle timeout is configurable; the lockout after 5 failed attempts is always 15 minutes |
| No public audit route | RD-3 — all `GET /api/audit/*` routes require an authenticated session |

## Auth module binding decisions

| Decision | Rationale |
|----------|-----------|
| Hybrid membership freshness | RD-1 — 5-minute cache for read paths; live Supabase check for sensitive paths |
| Artifact blobs encrypted; SQLite metadata columns plaintext | RD-2 — OS full-disk encryption covers the plaintext columns; application-layer encryption targets blobs only |
| Share links: token + 7-day expiry + view-cap + revoke + IP rate limit + per-org email-OTP toggle | RD-3 — all five controls implemented in Phase 2 |

## Pre-production requirements

- Full-disk encryption must be enabled on the host OS before handling sensitive artifact data (FileVault on macOS, LUKS on Linux, BitLocker on Windows).
- `MADARX_AUTH_MODE=local` is for development only. Production must set `MADARX_AUTH_MODE=supabase`.
- Run `npm run check:disk-enc` at boot to verify OS encryption state and write the health audit row.
- See `docs/deploy/production-checklist.md` for the complete pre-launch checklist.
