Tablet 45: the courtyard gate checks the key, not the shape of the header

/courtyard/* and /chatzer/* admitted any `Authorization: Bearer <x>`.
Every test sent `Bearer test-token`; none sent a wrong one. Every
admitted request spends the day's manna.

VavPipeline.BigdeiKodesh.Gate: keys are a file (Construction §4.6 —
never env, never config, never git), one `tier key` per line, tiers
courtyard < chatzer (the inner ring opens the outer). Constant-time
compare against every line; no file / empty / unreadable → nobody
passes, GATE_NO_KEYS in the log, the caller told only "missing or
invalid". 403 FORBIDDEN for an outer key at an inner gate. Read on
every request so a rotated key is live in ~1 min without a rollout.

Deploy: Secret shorashim-api-keys projected at
/etc/shorashim/secrets/api_keys (vav-pipeline only, 0440),
API_KEYS_FILE in the configmap, template + issue/rotate recipe in
secrets.yaml and KOHEN.md "Issuing a key". Spec: tamid §1.1 (gate,
403), vayakhel §4.6. Doorpost 91d09d8d… → 42b38626….
Tests: tablet45_gate_test (parse, tiers, wrong/near/empty/no key, no
file) — Elixir 527/0.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Scott KarpandClaude Fable 5.1 committed 2026-10-06 15:16:33 +00:00
1 parent 996a8efe68
commit 79eb34840f
17 files changed
+372 -34

No files matched your search

@@ -28,6 +28,8 @@ defmodule VavPipeline.BigdeiKodesh.Router do
require Logger
alias VavPipeline.BigdeiKodesh.Gate
# Form 3 architectural enforcement (vehaya §3.2). NoTrackingLogger
# replaces Plug.Logger; sanitized paths on /peah/* honor the
# no-tracking promise made in /peah/ask's own response body.
@@ -128,21 +130,35 @@ defmodule VavPipeline.BigdeiKodesh.Router do
end
# === COURTYARD — Authenticated ===
#
# The key is checked against the keys file (VavPipeline.BigdeiKodesh.Gate),
# not merely for its shape. Until 2026-10-06 any Bearer value passed.
defp through_the_gate(conn, tier, serve) do
case Gate.admit(conn, tier) do
{:ok, _granted} ->
serve.(conn)
post "/courtyard/offering" do
case get_req_header(conn, "authorization") do
["Bearer " <> _token] ->
process_courtyard(conn)
{:error, :tier_too_low} ->
send_error(conn, 403, "FORBIDDEN", %{
issue: "This key does not open the #{tier}",
correction: "Use a key issued for the #{tier} tier",
resubmit: false
})
_ ->
{:error, _} ->
send_error(conn, 401, "UNAUTHORIZED", %{
issue: "Missing or invalid Authorization header",
correction: "Include 'Authorization: Bearer <api-key>' header",
issue: "Missing or invalid API key",
correction: "Include 'Authorization: Bearer <api-key>' with a key issued by the kohen",
resubmit: true
})
end
end
post "/courtyard/offering" do
through_the_gate(conn, :courtyard, &process_courtyard/1)
end
# === COURTYARD — the bedikah (Construction §3.9) ===
#
# An external model's output, brought by hand to be examined against
@@ -152,33 +168,13 @@ defmodule VavPipeline.BigdeiKodesh.Router do
# engraved in L3. See VavPipeline.Bedikah.
post "/courtyard/bedikah" do
case get_req_header(conn, "authorization") do
["Bearer " <> _token] ->
process_bedikah(conn)
_ ->
send_error(conn, 401, "UNAUTHORIZED", %{
issue: "Missing or invalid Authorization header",
correction: "Include 'Authorization: Bearer <api-key>' header",
resubmit: true
})
end
through_the_gate(conn, :courtyard, &process_bedikah/1)
end
# === CHATZER — Full Access (Sanhedrin) ===
post "/chatzer/sanhedrin" do
case get_req_header(conn, "authorization") do
["Bearer " <> _token] ->
process_sanhedrin(conn)
_ ->
send_error(conn, 401, "UNAUTHORIZED", %{
issue: "Missing or invalid Authorization header",
correction: "Include 'Authorization: Bearer <api-key>' header",
resubmit: true
})
end
through_the_gate(conn, :chatzer, &process_sanhedrin/1)
end
# === KODESH — Operator Interface ===
@@ -954,6 +950,7 @@ defmodule VavPipeline.BigdeiKodesh.Router do
defp error_type("CLOUD" <> _), do: "system_transition"
defp error_type("HALT" <> _), do: "cardinal_sin_detected"
defp error_type("UNAUTHORIZED"), do: "authentication_required"
defp error_type("FORBIDDEN"), do: "insufficient_tier"
defp error_type("TEIKU" <> _), do: "unresolvable"
defp error_type("LO_TOV"), do: "specification_mismatch"
defp error_type("CHATAT" <> _), do: "correction_needed"