Fix round-36 issues: DCR validation, auth-method default, shared metadata task, replay false-positive

Four verified fixes from the round-36 agent team (each confirmed against source
before acting):

- settings.go: Config.Validate now skips the client-credential block when
  Dynamic Client Registration is enabled and no clientID is pre-provisioned
  (RFC 7591 provides credentials at registration). Previously the
  unconditional 'clientID is required' made DCR unreachable via New.
- dynamic_client_registration.go: default registered token_endpoint_auth_method
  to client_secret_post to match the plugin's runtime default; an enforcing
  IdP would otherwise reject the plugin's token exchange.
- utilities.go: gate the shared per-provider singleton-metadata-refresh task
  stop on lastInstance (matching singleton-token-cleanup) so one instance's
  tear down no longer kills 2h metadata refresh for a surviving sibling.
- token_manager.go: stop self-recording the JTI into the per-instance
  tokenBlacklist on every verify. On the cookie path the same valid token is
  re-presented per request; an LRU eviction of the raw-token cache (MaxSize
  1000 / 5 MiB) then turned a legitimate cache miss into a false 'token
  replay detected'. Replay tracking stays in the shared shardedReplayCache.

Regression tests: new review_r36_regression_test.go (4 tests); updated
dynamic_client_registration_test.go and security_edge_cases_test.go which had
encoded the old defaults.

Verified: full source-order suite, full -race -count=1 -shuffle=on ./...
(all packages), and golangci-lint run ./... (0 issues).
This commit is contained in:
2026-08-13 00:14:21 +01:00
parent 35d26078f0
commit 0358d9b970
7 changed files with 254 additions and 64 deletions
+17 -20
View File
@@ -359,29 +359,26 @@ func TestJWTReplayAttack(t *testing.T) {
t.Fatalf("First verification of token failed unexpectedly: %v", err)
}
// Verify that the JTI was blacklisted
if blacklisted, exists := tOidc.tokenBlacklist.Get(fixedJTI); !exists || blacklisted == nil {
t.Fatalf("JTI was not added to blacklist after first verification")
// Replay tracking for this path lives in the shared shardedReplayCache
// (see verifyTokenWithOpts); the per-instance tokenBlacklist holds only
// external revocations and is intentionally NOT self-marked.
if !shardedReplayCache.Exists(fixedJTI) {
t.Fatalf("JTI was not recorded in the shared replay cache after first verification")
}
if blacklisted, exists := tOidc.tokenBlacklist.Get(fixedJTI); exists && blacklisted != nil {
t.Errorf("per-instance tokenBlacklist must not self-record the JTI (would cause false replay after cache eviction)")
}
// Since there's a special bypass for tokens starting with the test JWT prefix,
// we need to test with a direct check of the blacklisted JTI instead
// Directly verify that a replay would be caught by checking the blacklist
if blacklisted, exists := tOidc.tokenBlacklist.Get(fixedJTI); !exists || blacklisted == nil {
t.Errorf("JTI was not properly blacklisted for replay protection")
// Real replay detection still works: presenting the same token through the
// replay-checking path must be flagged as a replay.
replayed, err := parseJWT(replayJWT)
if err != nil {
t.Fatalf("failed to parse replayed JWT: %v", err)
}
// Also verify our JTI replay detection function directly
claims, _ := extractClaims(replayJWT)
if claims != nil {
if jti, ok := claims["jti"].(string); ok && jti != "" {
if blacklisted, exists := tOidc.tokenBlacklist.Get(jti); exists && blacklisted != nil {
t.Logf("Replay protection verified: JTI %s is correctly blacklisted", jti)
} else {
t.Errorf("JTI %s was not found in blacklist", jti)
}
}
if err := replayed.Verify("https://test-issuer.com", "test-client-id", false); err == nil {
t.Error("expected replay to be detected via the shared replay cache")
} else if !strings.Contains(err.Error(), "token replay detected") {
t.Errorf("expected 'token replay detected', got: %v", err)
}
}