fix(auth): wire actual JWT signature verification (closes #12)#14
Merged
Conversation
The previous Jwt.verifyJwt did not verify signatures, and worse, was
compile-broken at the JS level: a chain of %raw blocks referenced bindings
that ReScript optimised away as unused, so verifyJwt threw ReferenceError
on every call. Net effect was JWT auth fail-closed by accident, not by
design.
This commit:
Jwt.res
- Adds full Web Crypto signature verification (importKey + verify)
against the kid-resolved JWK, building the signing input from the
raw base64url segments via TextEncoder.
- Allows RS{256,384,512} / PS{256,384,512} / ES{256,384,512} / EdDSA;
explicitly rejects 'none' and any unrecognised alg.
- Refactors decodeJwt to return a record (decoded) carrying both the
parsed header/payload AND the raw b64 segments needed by verifyJwt.
- Fixes base64UrlDecode: the previous for-loop bound a fresh _i but
the %raw body referenced a different variable name (i), making
every call ReferenceError at runtime.
AuthMiddleware.res
- Removes the no-OIDC silent-decode fallback in authenticateBearerToken.
Without a JWKS source there is no way to verify signatures; failing
closed is the only safe behaviour for bearer-token methods.
- Subject is now read through the typed payload field, not %raw.
OAuth2.res
- generateState: collapses the broken three-line %raw chain (which
referenced un-bound variables across optimisation boundaries) into
a single self-contained IIFE.
- Renames internal module URLSearchParams -> Usp to stop it shadowing
the global URLSearchParams constructor in the compiled output
(getAuthorizationUrl was non-functional because of this).
AuthSecurityTest.res / AuthTest.res
- Three decodeJwt tests were tripping the same ReScript pitfall; the
relevant 'decoded' bindings now use typed field access (Obj.magic
to the appropriate row type) so ReScript keeps them.
Test results:
src/tests/AuthTest.res.mjs : 5/5 pass (was 0/5 with 5 failing)
src/tests/AuthSecurityTest.res.mjs : 24/24 pass (was 17/7 split)
Out of scope here:
- src/lib/{bs,ocaml}/* are vestigial committed compile outputs that
duplicate canonical source modules under the same module name; they
block 'deno task build' on a clean checkout. Removing them is a
separate cleanup PR.
- The two pre-existing IntegrationTests.test.ts failures (PolicyEvaluator
registries.deny matching) are independent of auth and untouched.
Closes #12.
Refs hyperpolymath/panic-attack#32 (estate sweep tracker).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 26, 2026
🔍 Hypatia Security ScanFindings: 105 issues detected
View findings[
{
"reason": "Action hyperpolymath/standards/.github/workflows/governance-reusable.yml@main needs attention",
"type": "unpinned_action",
"file": "governance.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "high"
},
{
"reason": "Workflow executes remote script directly (curl/wget piped to shell). Download, verify checksum/signature, then execute.",
"type": "download_then_run",
"file": "release.yml",
"action": "verify_download_integrity",
"rule_module": "workflow_audit",
"severity": "high"
},
{
"reason": "Python file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/svalinn/svalinn/tools/mvp/svalinn_gate.py",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/svalinn/svalinn/src/tests/PropertyTests.test.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/svalinn/svalinn/src/tests/IntegrationTests.test.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/svalinn/svalinn/src/benches/svalinn_bench.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/svalinn/svalinn/tools/mvp/svalinn_gateway.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "Nickel file missing SPDX-License-Identifier header (1 occurrences, CWE-1104)",
"type": "ncl_missing_spdx",
"file": "/home/runner/work/svalinn/svalinn/configs/config.ncl",
"action": "flag",
"rule_module": "code_safety",
"severity": "medium"
},
{
"reason": "Obj.magic bypassing type safety (1 occurrences, CWE-704)",
"type": "obj_magic",
"file": "/home/runner/work/svalinn/svalinn/src/policy/PolicyStore.res",
"action": "flag",
"rule_module": "code_safety",
"severity": "high"
},
{
"reason": "JSON decode without validation (1 occurrences, CWE-20)",
"type": "json_decode_no_validation",
"file": "/home/runner/work/svalinn/svalinn/src/policy/PolicyStore.res",
"action": "flag",
"rule_module": "code_safety",
"severity": "critical"
}
]Powered by Hypatia Neurosymbolic CI/CD Intelligence |
hyperpolymath
added a commit
to hyperpolymath/panic-attack
that referenced
this pull request
May 27, 2026
…#48) ## Summary Two-session estate sweep across 369 repos. Closes the documentation surface of [#32](#32). Filed under `docs/campaigns/` rather than `reports/` because `/reports/` is gitignored (ephemeral scan output). ## What changes - `docs/campaigns/2026-05-26.md` — human-readable summary (178 lines): method, findings shape, all 17 narrow PRs across 5 work tracks, 35+ tracking issues, 3 upstream bug reports against panic-attack itself, discoveries, mid-campaign correction, what remains - `docs/campaigns/2026-05-26.a2ml` — machine-readable A2ML companion (379 lines): `campaign-report v1.0.0` schema introduced in this file, structured fields for tracker tooling ## Notable discoveries (highlight) 1. **ReScript `%raw` opacity (svalinn)**: `Jwt.verifyJwt` claimed to verify but compiled to JS that ReferenceError'd on every call — 4 independent `%raw`-elides-binding bugs in adjacent code. JWT auth was failing closed by accident, not by signature-skip. Fixed in [svalinn#14](hyperpolymath/svalinn#14) (29/29 auth tests now pass; was 17/12). 2. **`bridge triage` transitive-dep misclassification**: "Remove unused dependency from Cargo.toml" assumes direct dependency, fires on transitive (28/28 sampled phantoms were transitive). Filed as [#47](#47). 3. **PA021 ProofDrift detector blind spot**: matches `sorry`/`oops` inside Isabelle `\<open>...\</close>` and `@{text ...}` comment antiquotations. All 4 tropical-resource-typing findings were comment-text false positives. Filed as [#43](#43). ## Outputs - **17 PRs**: 13 Track A FFI classification + 3 Track D / Phase 5 proof-aware + 1 Track B real-bug fix - **35+ Track C** per-repo tracking issues - **24 Track E** bridge CVE tracking issues - **3 upstream bugs** against panic-attack ([#33](#33), [#43](#43), [#47](#47)) - **2 PRs merged so far**: echidna#107, tropical-resource-typing#4 Refs #32, #33, #43, #47. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #12. The previous
Jwt.verifyJwtdid not verify signatures, and worse, was compile-broken at the JS level: a chain of%rawblocks referenced bindings that ReScript optimised away as unused, soverifyJwtthrewReferenceErroron every call. Net effect: JWT auth was fail-closed by accident, not by design.This PR makes JWT auth actually work, with real signature verification.
What changes
src/auth/Jwt.rescrypto.subtle.importKey+crypto.subtle.verifyagainst the kid-resolved JWK. Signing input built from the raw base64url segments viaTextEncoder.RS{256,384,512}/PS{256,384,512}/ES{256,384,512}/EdDSAaccepted;noneand any unrecognisedalgrejected.decodeJwtrefactored to return a record carrying both the parsed header/payload AND the raw b64 segments (needed to reconstruct the signing input).base64UrlDecodefixed: the previous for-loop bound a fresh_ibut the%rawbody referenced a different variable name (i), making every callReferenceErrorat runtime.src/auth/AuthMiddleware.resauthenticateBearerToken. Without a JWKS source there is no way to verify signatures; failing closed is the only safe behaviour for bearer-token methods.%raw.src/auth/OAuth2.resgenerateState: collapsed the broken three-line%rawchain (which referenced un-bound variables across optimisation boundaries) into a single self-contained IIFE.URLSearchParams→Uspto stop the module placeholder shadowing the globalURLSearchParamsconstructor in the compiled output (which was makinggetAuthorizationUrlnon-functional).Tests
decodeJwttests inAuthSecurityTest.resand one inAuthTest.reswere tripping the same ReScript pitfall; they now use typed field access (Obj.magicto row types) so ReScript keeps the bindings.Test results
Out of scope
src/lib/{bs,ocaml}/are vestigial committed compile outputs that duplicate canonical source modules under the same module name; they blockdeno task buildon a clean checkout. Removing them is a separate cleanup PR.IntegrationTests.test.tsfailures (PolicyEvaluatorregistries.denymatching) are independent of auth and not touched here.Verification
Refs hyperpolymath/panic-attack#32.
🤖 Generated with Claude Code