H1: HMAC-gate internal endpoints /ticket /order /person (TDD)

Failing tests first (4 red: unsigned POSTs returned 200 and minted objects), then the gate:
host/blog--int-verify? checks x-int-sig = sess-sig(fed-secret, request TARGET) (params live in the
query, body is empty); host/blog--protect-internal wraps the three routes → 403 unsigned. Secret
unset = open (dev/tests). Callers (events→shop /ticket + /order, shop→identity /person) sign via
host/blog--int-headers. Closes the live capacity-bypass (anyone could mint tickets directly).

blog suite 225/225 (218 + 7 new).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-03 10:07:40 +00:00
parent a7533b26b1
commit 8e0f06aa28
2 changed files with 57 additions and 8 deletions

View File

@@ -1315,6 +1315,37 @@
(list before (map (fn (e) (get e "verb")) host/blog--flow-log)))))
(list (list "validate" "digest") (list "validate" "digest")))
;; ── HARDENING H1: internal endpoints (/ticket /order /person) are HMAC-gated ─────────────
;; With a fed-secret set, an UNSIGNED POST is 403 and creates nothing; a SIGNED one
;; (x-int-sig = sess-sig(secret, target)) works. Secret unset ("") = open (dev/test compat).
(host/blog-use-store! (persist/open))
(define host-bl-h1-secret "test-int-secret")
(host/blog--set-fed-secret! host-bl-h1-secret)
(define host-bl-h1-app (host/make-app (list host/blog-routes)))
(define host-bl-h1-post
(fn (target sig)
(host-bl-h1-app (dream-request "POST" target
(if sig {:x-int-sig (dr/sess-sig host-bl-h1-secret target)} {}) ""))))
(host-bl-test "H1: unsigned /ticket -> 403"
(dream-status (host-bl-h1-post "/ticket?showing=sh1&offering=sh1--adult&email=a@x.com" false)) 403)
(host-bl-test "H1: unsigned /ticket creates NO ticket"
(len (filter (fn (s) (starts-with? s "ticket-")) (host/blog-slugs))) 0)
(host-bl-test "H1: signed /ticket -> 200 ticket:*"
(starts-with? (dream-resp-body (host-bl-h1-post "/ticket?showing=sh1&offering=sh1--adult&email=a@x.com" true)) "ticket:")
true)
(host-bl-test "H1: unsigned /order -> 403"
(dream-status (host-bl-h1-post "/order?event=sh1" false)) 403)
(host-bl-test "H1: unsigned /person -> 403"
(dream-status (host-bl-h1-post "/person?email=a@x.com" false)) 403)
(host-bl-test "H1: signed /person -> 200 person:*"
(starts-with? (dream-resp-body (host-bl-h1-post "/person?email=a@x.com" true)) "person:")
true)
(host/blog--set-fed-secret! "")
(host-bl-test "H1: secret unset -> /person open (dev compat)"
(starts-with? (dream-resp-body (host-bl-h1-post "/person?email=b@x.com" false)) "person:")
true)
(define
host-bl-tests-run!
(fn ()