Repository navigation
fix: bound the JWKS refresh rate and reject an unknown symbol policy (ADR 0066) - #104
Merged
Merged
Conversation
…(ADR 0066) Two places where input the agent does not trust decided something quietly. The JWKS refetch. jwt.ParseWithClaims asks for the key before it checks the signature, and the handler verifies before it reads the body — so the kid comes from an unverified token and an unauthenticated caller chose how often the controller queried its own API server, two GETs at a time, serialized behind one mutex with a 10s ceiling each. Refreshes now happen at most once per 30s, and the stamp is taken before the attempt so a failing refresh is bounded too. The cost is a rotation picked up up to one node scan round late, on a payload that is re-sent by the next pass. The symbol policy. profiling.thirdPartySymbols was compared to "keep" and everything else took the other branch, so "Keep" and every typo silently meant "drop". It governs which frames may leave the node, so an unrecognized value now fails the start, the same shape as ADR 0040's rule for an unset expectedSubject. Also carries the .gitignore fix that PR #103 was meant to land and did not: an unanchored `coverage.*` still matches a source file in any directory. An existing test needed the clock advanced to keep proving its own claim: with the floor in place, "the refresh failed" and "no refresh was attempted" look alike from outside, so it now counts attempts on the source. go test -race ./... green; golangci-lint 0 issues; chart-lint and vulncheck clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011YRKuL97KRndaopfpixrhp
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.
Two places where input the agent does not trust decided something quietly.
Reasoning, alternatives and what changes for an operator are in
ADR 0066.
The JWKS refetch
jwt.ParseWithClaimsasks for the key before it checks the signature, andthe handler calls
Verifybefore it reads the body. So thekidcomes outof a token nobody has verified, and each distinct value a caller invented cost
two GETs to the API server — discovery and JWKS — serialized behind
CachingKeySource's one mutex with a ten-second ceiling each. The refresh ratewas a property of the caller.
Refreshes now happen at most once per 30s. The stamp is taken before the
attempt, so a refresh that failed is rate-limited on the same floor — an
unreachable API server is where retrying per request costs the most and helps
least. Against the node's one-minute scan interval, a key rotation is picked up
at most one round late, on a report the next pass re-sends anyway (ADR 0003).
The chart's NetworkPolicy already bounds who can reach the port (ADR 0039); that
is who, not how often, and a policy the CNI does not enforce is the case worth
surviving.
The symbol policy
profiling.thirdPartySymbolswas compared to"keep"and everything else tookthe other branch — so
"Keep","true"and every typo silently meantdrop.Failing closed is the right direction and the wrong report: this field governs
the allow-list deciding which frames may leave the node (ADR 0011 §4), and it is
the one control an operator cannot check by reading their own values file. An
unrecognized value now fails the start, the same shape as ADR 0040's rule for an
unset
expectedSubject. Empty stays valid — it selects the default and is whata default install renders.
One test had to change to keep proving its claim
TestAFailedRefreshKeepsTheKeysAlreadyInHandpassed unchanged, but for thewrong reason: with the floor in place its second lookup no longer attempted a
refresh at all, and "the refresh failed" and "no refresh was made" leave the
server's hit count identical. It now advances the clock past the floor and
counts attempts on the source itself.
Also carried here
The
.gitignorefix that #103 was meant to land and did not — I staged it andthen ran a stray
git stash/git stash popbefore committing, which unstages,so only
internal/model/coverage.gowent in.origin/mainstill has anunanchored
coverage.*that matches a source file in any directory and swallowsit in silence. One line, and it belongs on main more than it belongs in a third
PR of its own.
Checks
No
docs/security.mdchange: nothing collected, stored or transmitted differs.🤖 Generated with Claude Code
https://claude.ai/code/session_011YRKuL97KRndaopfpixrhp