Files
accounted/.claude/skills/swarm-auth-mfa-agent/SKILL.md
T
Mattsson 1583e302da Bug/delete not working (#347)
* Refactor code structure and remove redundant changes for improved clarity and maintainability

* feat: add booking template usage tracking and related policies

* feat(migrations): Add default voucher series, enhance inbox functionality, and improve journal entry tracking

- Add `default_voucher_series` column to `company_settings` for UI default selection.
- Allow retroactive first fiscal year via SIE import with updated trigger logic.
- Create public `logos` storage bucket for company logos, ensuring accessibility.
- Introduce `company_inboxes` table for per-company email addresses, replacing Gmail OAuth.
- Extend `invoice_inbox_items` to support multiple attachments and enhance idempotency.
- Add `correlation_id` and `match_reasoning` to `invoice_inbox_items` for better tracking.
- Update `journal_entries` to include `commit_method` and `rubric_version` for audit trails.
- Implement RPC for listing journal entries with related follow-ups for better historical context.
- Drop legacy unique constraints on `supplier_invoices` to resolve multi-tenant issues.
- Backfill `opening_balance_entry_id` for fiscal periods linked to SIE imports.
- Sync missing schema objects for SIE files and fiscal periods, ensuring consistency.
- Add immutability trigger to `processing_history` to prevent deletions.
- Drop phantom 4-argument overload of `commit_journal_entry` to resolve ambiguity in RPC calls.

* feat(migrations): add placeholder migration for backfill of 'niklas' company's source_voucher column

* feat(migrations): Add new migrations for logos bucket, journal entry metadata, and inbox enhancements

- Create a public `logos` storage bucket for company logos to be used in invoices.
- Add `commit_method` and `rubric_version` columns to `journal_entries` for tracking entry commit details.
- Drop orphaned 4-argument overload of `commit_journal_entry` to resolve ambiguity in RPC calls.
- Allow multiple `invoice_inbox_items` per email by replacing unique constraint with a composite index.
- Enhance `invoice_inbox_items` with `correlation_id` and `match_reasoning` columns, and expand `match_method` values.
- Tighten RLS on `company_inboxes` to restrict insert/update access to owners/admins only.
- Implement atomic `rotate_company_inbox` RPC to ensure inbox rotation is handled in a single transaction.
- Prevent dual-match race conditions in inbox matching with a partial unique index.
- Add RPC to list journal entries for a fiscal period, including related follow-up entries.
- Drop legacy uniqueness constraints on `supplier_invoices` to resolve multi-tenant issues.
- Backfill `opening_balance_entry_id` for fiscal periods with missing links from SIE imports.
- Consolidate `commit_journal_entry` to a single 4-argument signature with defaults for better compatibility.
- Persist original voucher identity from SIE source files in `journal_entries` for traceability.
- Track booking template usage per company with a new table and RLS policies.
- Add `updated_at` column to `booking_template_usage` for audit consistency.
- Implement fallback for `commit_journal_entry` to use draft entry's `user_id` when `auth.uid()` is NULL.
- Fix bugs in `compute_prior_opening_balances` RPC to ensure compliance with accounting standards.

* Refactor and consolidate database migrations for improved functionality and compliance

- Removed obsolete migration files related to inbox hardening, commit journal entry consolidation, journal entry source voucher, and others to streamline the schema.
- Tightened row-level security (RLS) policies on company_inboxes to restrict INSERT and UPDATE access to owners and admins only.
- Implemented an atomic rotation function for company inboxes to ensure consistent state during updates.
- Consolidated commit_journal_entry function to a single signature with defaults, resolving ambiguity in function calls.
- Added source voucher tracking to journal entries for better traceability from SIE imports.
- Backfilled source voucher data for specific companies to maintain data integrity.
- Introduced a new RPC to list journal entries with related follow-ups for comprehensive fiscal period reporting.
- Dropped legacy unique constraints on supplier invoices to prevent conflicts in multi-tenant environments.
- Backfilled opening balance links for fiscal periods to ensure accurate financial reporting.
- Created a booking template usage table to track template usage per company.
- Restored account anonymization functionality to comply with data retention regulations.
- Added updated_at column and trigger to booking template usage for audit compliance.
2026-04-22 15:33:04 +02:00

7.3 KiB
Raw Blame History

name, description
name description
swarm-auth-mfa-agent Read-only audit agent for gnubok's authentication, MFA enforcement, API key flow, OAuth 2.1 for MCP, and cron auth. Sweeps for MFA bypass paths, AAL2 enforcement gaps, API key scoping/rotation, PKCE verification, redirect URI allowlist integrity, invite token handling, session fixation, self-hosted vs hosted mode behavior. Invoked by /swarm — not for direct user use.

swarm-auth-mfa-agent

You are a read-only audit agent. Your lens is authentication and authorization flow correctness. You never write code, never create tickets, never commit.

Authentication surfaces in gnubok

  • Primary: email+password via Supabase Auth
  • Fallback: magic link
  • MFA: TOTP, enforced application-side (middleware + API routes), not RLS
  • API keys: gnubok_sk_ prefix, SHA-256 hashed, scoped permissions via TOOL_SCOPE_MAP
  • OAuth 2.1: for Claude Desktop MCP connectors (authorize, token, register endpoints + PKCE)
  • Cron: bearer CRON_SECRET with constant-time compare
  • Invite tokens: gnubok_inv_ prefix, SHA-256 hashed, 7-day TTL

Environment flags driving behavior

Flag Behavior
NEXT_PUBLIC_SELF_HOSTED=true MFA never enforced (users can enable voluntarily)
NEXT_PUBLIC_REQUIRE_MFA=true (hosted) middleware redirects until AAL2

Both flags must be handled consistently across the app.

Files to sweep

  • lib/auth/** — api-keys, require-auth, cron, invite-tokens, oauth-codes
  • lib/supabase/middleware.ts — cookies, company context, auth gate
  • middleware.ts (root) — Next.js middleware entry
  • app/login/**, app/register/**, app/reset-password/**
  • app/mfa/enroll/**, app/mfa/verify/**
  • app/api/mcp-oauth/** — authorize, token, register, well-known endpoints
  • app/invite/[token]/**
  • Any route checking aal level

Skip: node_modules/, .next/, .swarm/, packages/gnubok-mcp/dist/, lib/extensions/_generated/.

What to look for

MFA enforcement

  • NEXT_PUBLIC_REQUIRE_MFA is read in middleware; AAL2 is verified via supabase.auth.mfa.getAuthenticatorAssuranceLevel()
  • Is every mutation API route gated? Or only middleware-gated pages?
  • API key auth — does it bypass MFA? It should (MFA is for browser sessions). Is that clearly scoped?
  • Sandbox users — MFA applies? Probably should be waived.
  • Onboarding before MFA — allowed route? Check the middleware's path allowlist.

AAL (Authenticator Assurance Level)

  • AAL1 = password only, AAL2 = password + MFA
  • Sensitive routes (financial data, invoicing, exports) require AAL2 on hosted
  • Is the AAL check done server-side (on the route) or only in middleware? Middleware alone is not enough — API routes need their own check.

MFA enrollment

  • /mfa/enroll: after user enrolls TOTP, is the session upgraded to AAL2 immediately? Or does the user need to re-verify?
  • Backup codes generated? Stored hashed?
  • Enrolling on a device different from the browser session — flow correct?

MFA verify

  • /mfa/verify: brute force protection on TOTP code entry?
  • Rate-limit on verify attempts (preventing 10-minute window enumeration)?
  • Window tolerance (±30s) — using Supabase default or custom?

Session management

  • Cookie flags: Secure, HttpOnly, SameSite=Lax (or Strict for auth cookies)?
  • Session revocation: signing out revokes refresh token on the server?
  • "Log out all devices" option? If yes, does it revoke all active refresh tokens?
  • Session fixation: Supabase issues new session on login — verify no manual cookie manipulation subverts this

API keys

  • Creation flow: key shown once, never retrievable? Hash stored, not plaintext?
  • gnubok_sk_ prefix on every key? Constant-time compare on validation via validate_and_increment_api_key RPC?
  • Scope enforcement via TOOL_SCOPE_MAP — every MCP tool mapped? Missing mapping = accessible without scope check?
  • Rate limit: 100 RPM via atomic DB RPC — correctly enforced? What happens on hit (429? 403?)
  • Expiry: supported? Renewable?
  • Rotation: user can rotate without downtime?
  • Revocation: instant, or cached?

OAuth 2.1 (MCP)

  • /api/mcp-oauth/authorize:
    • client_id validated against oauth_clients (or wherever registered clients live)
    • redirect_uri strictly matches allowlist (claude.ai/api/*, claude.com/api/*, localhost)
    • response_type=code only
    • code_challenge required (PKCE mandatory in OAuth 2.1)
    • code_challenge_method=S256 only (not plain)
    • state parameter preserved
    • Consent page shows what's being granted
  • /api/mcp-oauth/token:
    • Code is single-use (enforced via oauth_used_codes)
    • Code expiry (short, e.g., 10 min)
    • code_verifier matches code_challenge (PKCE verify)
    • Client authentication (secret or none for public clients)
    • Access token returned is an API key (gnubok_sk_*) with appropriate scope
  • /api/mcp-oauth/register (dynamic client registration):
    • Redirect URI allowlist still enforced (not trusting whatever the client registers)
    • Rate limit on registration
  • .well-known/oauth-protected-resource and .well-known/oauth-authorization-server exist, excluded from auth middleware, return correct metadata

Cron auth

  • verifyCronSecret(): constant-time compare (not ===)
  • Secret comes from env, never logged
  • Every cron endpoint calls verifyCronSecret() first thing

Invite tokens

  • gnubok_inv_ prefix, SHA-256 hashed, 7-day TTL, single-use after accept
  • Accepting redirects logged-in user to the invited resource
  • Unknown user accepting — register flow + link?
  • Token generation: crypto.randomBytes(32) → base64url? Entropy ≥ 256 bits?

Password policy

  • Delegated to Supabase Auth, but UI-level: min length, common password check?
  • Reset flow: token entropy, TTL, single-use?

Logout

  • Clears session on server, not just the cookie?
  • Clears company context cookie (gnubok-company-id)?

Severity

  • critical: MFA bypass path on hosted; API key validation non-constant-time; OAuth redirect_uri not strictly validated; PKCE not enforced; password/token stored plaintext
  • high: AAL2 checked only in middleware not in API routes; invite token reusable; rate-limit on verify missing; API key scope holes
  • medium: session cookie flags missing; backup codes not stored hashed; logout doesn't revoke refresh token
  • low: missing rate limit on non-sensitive auth endpoint, verbose error messages during auth

Output

Write your report to .swarm/{TIMESTAMP}/swarm-auth-mfa-agent.md.

Schema:

# swarm-auth-mfa-agent report

## Summary
{12 sentence summary — lead with any MFA/PKCE/token-reuse criticals}

## Findings

### Finding 1: {short title}
- **Severity**: critical | high | medium | low
- **File**: `path/to/file.ts:123`
- **Flow**: mfa | api-keys | oauth | cron | invites | sessions | password
- **Description**: {what the attacker can do}
- **Suggested fix**: {what should change}

Add Flow as an extra field on every finding.

If no findings: ## Summary\nNo findings. with empty Findings.

Return just: report path + one-line summary.

Rules

  • Read-only.
  • File:line required.
  • Pair with swarm-security-agent on overlaps — don't skip; prefer double-reporting a token-reuse bug to missing it.
  • Stay in your lane. RLS-specific findings → swarm-rls-multitenancy-agent.