# Audit

> Auditing a Forseti deployment

- Skill: `franzos/audit` (Agent Skill)
- Install (CLI): `npx skillmds@latest add franzos/audit`
- Raw SKILL.md: https://api.skillmd.com/api/skills/franzos/audit/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: franzos (https://skillmd.com/u/franzos)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/franzos/audit

---


# Auditing a Forseti deployment

Produce a ranked findings list, not a lecture. Check the live config against the deployment's own tooling and the operator guide, rank by severity, and let the user choose what to fix. Read the config that is actually deployed; do not assume the example files.

## 1. Locate the config and the deployment shape

Find the active `config.toml` (`$FORSETI_CONFIG_PATH`), `infra/kratos/kratos.yml`, `infra/hydra/hydra.yml`, and how it is run (single binary vs multi-instance, SQLite vs Postgres, behind a proxy or not). Rationale for each item below is in `docs/operator-guide.md`.

## 2. Run the built-in linter

`forseti config-check --strict` lints the Ory config and is the fastest source of real findings. Fold its output into the punch-list.

## 3. Walk the checklist

For each item, record pass/fail and severity. Highest-severity items first:

- **2FA enforcement (critical).** In `kratos.yml`, both `session.whoami.required_aal` and `selfservice.flows.settings.required_aal` must be `highest_available`. `settings.required_aal: aal1` opens the factor-removal bypass (an attacker with the password or recovery email strips the second factor). Forseti cannot enforce this from its side.
- **Secrets rotated and unique.** Hydra `secrets.system` and pairwise salt; Kratos `secrets.cookie` and `secrets.cipher` (exactly 32 chars); Forseti `[security].cookie_secret` set to a stable ≥32-byte value (not the ephemeral per-boot key). None committed to the repo.
- **No placeholder secrets in the live config.** Confirm no OAuth `client_secret`, audit token, or SSH/SAML key was carried over from an example or playground config; rotate anything that was.
- **Audit webhook token.** `[audit].webhook_token` set and matching the bearer in every `web_hook` in `kratos.yml` (Forseti refuses to boot if empty, but confirm it is not the dev placeholder).
- **Internal listener not public.** `[internal].bind` on loopback only. It carries the audit receiver and the POSIX resolver (your directory); the network ACL is load-bearing once it leaves loopback.
- **Forwarded-for trust.** `[proxy].trust_forwarded_for = true` only behind a proxy that strips inbound forwarded-for headers, else IP spoofing bypasses rate limits.
- **TLS and headers.** Reverse proxy terminates TLS and passes `X-Forwarded-Proto: https` + `X-Forwarded-Host`; CSP `frame_ancestors` / `X-Frame-Options` set for clickjacking defense.
- **Webhook signing key.** `data/webhook-signing-key.pem` present, `0600`, valid Ed25519 PKCS#8, and backed up (rotation is stop-replace-restart).
- **Admin surface.** `[admin].allowed_emails` is the intended operator set and non-empty; confirm the two-tier operator/org-owner model is understood. Admin requires AAL2 in code.
- **Database and backups.** Postgres (not SQLite) for multi-instance, or corruption results. Kratos and Hydra Postgres backed up (losing Hydra's JWKS invalidates every issued id_token).
- **DCR hardening.** `[oauth].dcr_reserved_names` denylist populated and DCR rate limits set.
- **Metrics.** `/metrics` gated by license and `[metrics].scrape_token`, and served only on the internal listener.
- **Retention scheduled.** `forseti audit-prune` and `forseti unverified-prune` run on a timer/cron; they do not run automatically.
- **Log hygiene.** Kratos `log.leak_sensitive_values: false` outside dev; secrets supplied via env, not TOML; email creds never over `tls = "none"`.

## 4. Report

Present a numbered, severity-ranked punch-list (critical first), one line per finding with the offending file/key and the fix, in the style of the `rust:review` / `security:review` skills. Then offer to apply the fixes the user selects. Do not change config as part of the audit itself unless asked.

