Repository navigation
scheduledauth: enforce HTTPS scheme on SCHEDULED_TASK_OIDC_JWKS_URL #66
Description
Activity
- addedtriagedItem has been triagedItem has been triagedpriority/p2Backlog-worthyBacklog-worthyseverity/mediumModerate harmModerate harmurgency/this-quarterWithin the quarterWithin the quarterimpact/internalTeam-internal onlyTeam-internal onlyeffort/xsTrivial / one-linerTrivial / one-linertype/securitySecurity findingSecurity finding
on Jun 26, 2026 Reviewed commit:
be11bdcb5. Note:origin/mainmoved to3e9660d06during the review; re-verify against currentmainbefore changing code, since a finding may have been fixed or moved.The 2026-07-28 full-repo review re-derived this independently and confirmed it is still present. Two additions: an in-tree precedent that settles the "should we require https" question, and the reachability argument, which suggests
severity/mediumis understated.Where
internal/server/scheduledauth/validator.go:217(validateAbsoluteURL), called frominternal/server/scheduledauth/validator.go:145- Contrast:
internal/oidc/issuer_cache.go:38(oidc.SetIssuerURL)
What the review adds
The codebase already enforces the stricter rule one package over.
oidc.SetIssuerURLrejects anything that is not an absolutehttpsURL. So CUDly enforces https on its own issuer URL and not on the URL it fetches trusted verification keys from. That asymmetry is the whole finding, and it means the fix is a copy of an existing validator rather than a new policy decision.The blast radius is money movement, not just token forgery.
/api/scheduled/*is the surface that fires purchase executions. If an attacker substitutes the JWKS,go-oidcverifies attacker-signed RS256 tokens as genuine; issuer, audience and subject are all string claims inside a token the attacker now controls, so every remaining check passes. There is no second gate behind the token.Failure scenario
An operator sets
SCHEDULED_TASK_OIDC_JWKS_URLto anhttp://endpoint: an internal mirror, a cached copy behind a corporate proxy, or a test double left in place after a staging exercise. An attacker with a network position on that path, or DNS control over the host, serves their own JWKS. Every subsequent/api/scheduled/*call they make is accepted as an authenticated scheduled task, which yields unauthenticated purchase execution.The failure is silent in both directions: the operator's config looks correct, and nothing in the logs distinguishes a substituted key set from a legitimate one.
Fix direction
As already sketched here: require
u.Scheme == "https"invalidateAbsoluteURL, with an explicit localhost carve-out if the test suite needs one, matchingSetIssuerURL. Given the effort isxsand the outcome is unauthenticated money movement, this looks worth pulling forward fromurgency/this-quarter.
Context
Spun out of the adversarial review of PR LeanerCloud/cloud-commitments-cli#1231 (SEC-04, hardened HTTP client for JWKS warmup and fetch). Filed separately because the in-scope SEC-04 work was the move from
providers/azure/internal/httpclienttopkg/httpclientwith IMDS-blocking parity; this is additional hardening on top.Gap
internal/server/scheduledauth.validateAbsoluteURLaccepts any scheme with a non-empty host:So
SCHEDULED_TASK_OIDC_JWKS_URL=http://www.googleapis.com/...would pass. JWKS responses sign every inbound JWT inoidcmode; cleartext retrieval is a classic MITM vector.Fix sketch
In
validateAbsoluteURL(or a dedicated validator for the JWKS URL specifically), requireu.Scheme == "https". Test that:http://...is rejected at startup with a clear errorhttps://...is acceptedGoogleJWKSURL(which is already https)Closing-issue trailer for the fix PR