feat(auth): X-Bn-Verified-Captcha trusted header + CaptchaVerified helper
The host verifies (and consumes) a request's cap-token against its stateful captcha server before wasm dispatch and stamps this header; guests enforce captcha by checking CaptchaVerified, fail closed. Included in AllTrustedHeaders so the host's strip-then-stamp loop makes it unforgeable, like the identity headers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
54716d705f
commit
9a4394a07c
@ -36,6 +36,13 @@ const (
|
||||
HeaderVerifiedPublicUserID = "X-Bn-Verified-Public-User-Id"
|
||||
HeaderVerifiedPublicUsername = "X-Bn-Verified-Public-Username"
|
||||
HeaderVerifiedPublicEmail = "X-Bn-Verified-Public-Email"
|
||||
|
||||
// Captcha proof. The host verifies (and consumes — replay-protected) the
|
||||
// request's cap-token against the instance captcha server before dispatch
|
||||
// and stamps "1" on success. A guest cannot hold the host's stateful
|
||||
// verifier, so this header is the ONLY way to enforce captcha on a plugin
|
||||
// HTTP endpoint; render the widget with the ninjatpl captcha tag.
|
||||
HeaderVerifiedCaptcha = "X-Bn-Verified-Captcha"
|
||||
)
|
||||
|
||||
// AllTrustedHeaders lists every trusted identity header, canonical form. The
|
||||
@ -49,9 +56,16 @@ func AllTrustedHeaders() []string {
|
||||
HeaderVerifiedPublicUserID,
|
||||
HeaderVerifiedPublicUsername,
|
||||
HeaderVerifiedPublicEmail,
|
||||
HeaderVerifiedCaptcha,
|
||||
}
|
||||
}
|
||||
|
||||
// CaptchaVerified reports whether the host verified a captcha token for this
|
||||
// request. Fail closed: enforcing handlers must reject when this is false.
|
||||
func CaptchaVerified(h http.Header) bool {
|
||||
return h.Get(HeaderVerifiedCaptcha) == "1"
|
||||
}
|
||||
|
||||
// ContextFromTrustedHeaders rebuilds the request's auth context from the host's
|
||||
// verified identity headers. Trust model: see the const block above — these
|
||||
// headers are host-controlled, so no token parsing or signature check happens
|
||||
|
||||
@ -80,3 +80,27 @@ func TestTrustedHeaderMiddleware(t *testing.T) {
|
||||
t.Fatalf("middleware did not install principal: %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCaptchaVerifiedReadsTrustedHeader(t *testing.T) {
|
||||
h := http.Header{}
|
||||
if CaptchaVerified(h) {
|
||||
t.Fatal("CaptchaVerified() = true with no header, want false")
|
||||
}
|
||||
h.Set(HeaderVerifiedCaptcha, "1")
|
||||
if !CaptchaVerified(h) {
|
||||
t.Fatal("CaptchaVerified() = false with host-stamped header, want true")
|
||||
}
|
||||
h.Set(HeaderVerifiedCaptcha, "true")
|
||||
if CaptchaVerified(h) {
|
||||
t.Fatal(`CaptchaVerified() = true for non-"1" value, want false`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllTrustedHeadersIncludesCaptcha(t *testing.T) {
|
||||
for _, h := range AllTrustedHeaders() {
|
||||
if h == HeaderVerifiedCaptcha {
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Fatalf("AllTrustedHeaders() missing %s — hosts strip-then-stamp from this list, so omitting it makes the header client-forgeable", HeaderVerifiedCaptcha)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user