{"slug": "ai-assisted-authz-review-reading-permission-boundaries-in-ory-kratos", "title": "AI-Assisted AuthZ Review: Reading Permission Boundaries in Ory Kratos", "summary": "A developer used AI to generate authorization hypotheses for Ory Kratos, then manually killed each one, producing a kill table instead of false-positive reports. The review found no vulnerabilities in the repo-only analysis, with all five hypotheses—including admin API authorization, cross-tenant reads, token reuse, settings-flow identity confusion, and tenant-from-payload—killed by design or implementation. The approach inverts typical AI security scanning by prioritizing hypothesis elimination over bug discovery.", "body_md": "*Second in a series on using AI to review authorization — not to spray reports.*\n\n*Companion reference: AuthZ Smell Catalog.*\n\nThe cheapest thing an AI can do in security is generate suspicion. Point a model at a\n\ncodebase and it will hand you fifty \"possible IDORs\" before you finish your coffee. Almost\n\nall of them are wrong — guarded three lines up, scoped at the data layer, or protected at a\n\nboundary the model never saw.\n\nThat flood is exactly why several bug bounty programs spent 2026 tightening or pausing:\n\nthey were drowning in confident, plausible, wrong reports.\n\nSo this review inverts the usual loop. The AI's job is not to find bugs — it is to\n\n**over-generate hypotheses cheaply**. My job is to **kill** them. What survives that killing\n\nis the only thing worth a human's time, and the record of what died is more useful than the\n\nrecord of what lived.\n\nThe artifact of an honest review is therefore not a finding. It's a **kill table**.\n\n`repo_only`\n\n, and I say so explicitly rather than implying it reaches a live product.\n\nWhat this review does and does not claim.In this limited, repo-only review, the\n\nhypotheses I tested were killed. This isnota claim that Kratos has no vulnerabilities,\n\nand it is not a security audit. It is a case study in how AI-assisted AuthZ review can\n\navoid false positives — how tokilla suspicion instead of shipping it.\n\nI let cheap finders over-generate against the AuthZ Smell Catalog. The raw candidate\n\nlist, unfiltered:\n\n`/sessions/whoami`\n\n, identity lookups, or an admin identity fetch? Five confident hypotheses. This is the part AI is good at. Now the part it can't do.\n\nThe rule: **assume each is by-design until a concrete test says otherwise, and default to\nkilling it.** For source-only review, the \"test\" is: can I trace a\n\n**H1 — Admin API \"missing\" authorization → KILLED (by design).**\n\nKratos deliberately ships the admin API with *no* built-in authorization. Ory's own\n\ndocumentation states the admin API must be protected at the network boundary (ingress, a\n\nreverse proxy, Oathkeeper) and never exposed publicly. So \"no authz check in the handler\" is\n\nnot a missing guard — it is the guard living one layer out, exactly the false-positive shape\n\nin Catalog **§13 (middleware/deployment-layer authorization)**. A report of \"admin API allows\n\nidentity CRUD without auth\" is by-design and would be closed as such. Killed.\n\n**H2 — Cross-identity / cross-tenant read → KILLED (chokepoint design).**\n\nThis is the interesting one. Kratos does not scatter tenant checks across handlers. Its\n\npersistence layer runs every query through a **network Contextualizer** that injects the\n\nnetwork id (`nid`\n\n) into the SQL — the data-access layer itself filters by tenant, centrally.\n\nA handler *cannot* accidentally read across the boundary, because the boundary is enforced\n\nbelow the handler, at the one place every read funnels through. On the public API, identity\n\naccess is derived from the *session's* identity, never from a client-supplied id. To break\n\nH2 you would have to find a read path that bypasses the persister entirely — and I found no\n\nuser-reachable one in this build. Killed. And worth noting as a pattern: concentrating the\n\ntenant filter at the data-access layer collapses the whole class into a single auditable\n\npoint — which is why these particular hypotheses died here (Catalog §B).\n\n**H3 — Token reuse → KILLED.**\n\nRecovery and verification tokens are single-use and time-boxed; redemption invalidates the\n\ntoken in the same transaction. Replay after use fails. Killed.\n\n**H4 — Settings-flow identity confusion → KILLED.**\n\nThe settings flow binds to the identity resolved from the authenticated session. The identity\n\nbeing modified is not taken from client input, so you cannot retarget the flow at someone\n\nelse's traits. Killed (Catalog §02 — read-reachability is not write-reachability, and here\n\neven read is session-bound).\n\n**H5 — Tenant from payload → KILLED.**\n\nThe network id is derived from context, not from the request body. An admin create/update\n\ncannot smuggle a foreign `nid`\n\n. Killed.\n\nThe deliverable of the whole review, on one screen:\n\n| # | Hypothesis | Catalog | Verdict | Why it died |\n|---|---|---|---|---|\n| H1 | Admin API missing authz | §01, §13 | by-design |\nauthz is at the network boundary, not the handler — documented |\n| H2 | Cross-identity / cross-tenant read | §04, §05 | defended |\n`nid` enforced at the persister via the Contextualizer; public reads are session-bound |\n| H3 | Recovery/verification token reuse | §09 | defended |\nsingle-use, time-boxed, invalidated on redemption |\n| H4 | Settings-flow identity confusion | §02, §07 | defended |\nflow bound to the session identity, not client input |\n| H5 | Tenant assignment from payload | §04 | defended |\n`nid` from context, not request body |\n\nFive hypotheses in. Zero findings out. **This is a successful review, not a failed one** —\n\nand to be exact, it is a successful review *of five hypotheses*, not a clean bill of health\n\nfor Kratos.\n\nTwo shapes here generalize far beyond Kratos:\n\nNone of the hypotheses I tested survived source-only review — and the *reason* is worth\n\npublishing: Kratos concentrates its tenant boundary in one place (the persister's\n\nContextualizer) and derives identity from the session rather than from client input. That\n\ndesign choice is precisely what made four of my five hypotheses collapse to one question, and\n\nthat question had a clean answer.\n\nIf I were to keep going, the only honest next move would be to enumerate every ingress that\n\ncould reach persisted data *without* the persister — background jobs, imports, any raw query.\n\nIn the OSS build there is no user-reachable one. That negative result is real signal, and it\n\nis tier `repo_only`\n\n: I am not claiming it holds against any specific hosted deployment.\n\n`repo_only`\n\nis not `hosted_confirmed`\n\n. Say which\none you have. Conflating them is how OSS reading turns into a false bounty claim.Each kill sharpened a catalog entry's *confirm/kill* column — the column that separates a real\n\nbug from a by-design behavior:\n\nThe catalog is not a static list; every real outcome — even a clean by-design result — feeds a\n\nsharper kill test back into it. That feedback loop is the asset, not the entry count.\n\nThis review produces exactly one row for the outcome ledger — the honest kind, a defended\n\ntarget:\n\n```\ndate=2026-07-04, program=Ory Kratos (self-directed OSS review), source_type=oss_source_available,\nclass=tenant_boundary, repro_tier=repo_only, human_verdict=by_design, final_status=not_applicable,\npayout_usd=0, lesson=\"Contextualizer/nid chokepoint concentrates the tenant boundary; admin-API\nauthz is deployment-layer by design — both are KILLs, not bugs. Review collapses to: can anything\nreach persisted data without the persister?\"\n```\n\nRow #1 in a ledger is not supposed to be a payout. It's supposed to be *true*. From here, the\n\nnext step is a single source-available target with a **newly-added** permission boundary\n\n(a fresh RBAC, workspace, billing, or SSO/SCIM feature) — the un-picked-over surface — run\n\nthrough the same over-generate-then-kill loop, and logged as ledger row #2. One target. Not ten.\n\n*I use AI to reject candidates and humans to verify the few that survive. If that approach is\nuseful to you, the AuthZ Smell Catalog is the companion reference this series builds on.*", "url": "https://wpnews.pro/news/ai-assisted-authz-review-reading-permission-boundaries-in-ory-kratos", "canonical_source": "https://dev.to/fdjedkdlsspec/ai-assisted-authz-review-reading-permission-boundaries-in-ory-kratos-31cg", "published_at": "2026-07-04 00:12:17+00:00", "updated_at": "2026-07-04 00:18:41.491688+00:00", "lang": "en", "topics": ["ai-safety", "ai-research", "developer-tools"], "entities": ["Ory Kratos", "AuthZ Smell Catalog", "Ory"], "alternates": {"html": "https://wpnews.pro/news/ai-assisted-authz-review-reading-permission-boundaries-in-ory-kratos", "markdown": "https://wpnews.pro/news/ai-assisted-authz-review-reading-permission-boundaries-in-ory-kratos.md", "text": "https://wpnews.pro/news/ai-assisted-authz-review-reading-permission-boundaries-in-ory-kratos.txt", "jsonld": "https://wpnews.pro/news/ai-assisted-authz-review-reading-permission-boundaries-in-ory-kratos.jsonld"}}