Skip to content

Fix: remediate security audit findings in authbridge#496

Merged
huang195 merged 2 commits into
rossoctl:mainfrom
varshaprasad96:fix/security-audit-remediations
Jun 12, 2026
Merged

Fix: remediate security audit findings in authbridge#496
huang195 merged 2 commits into
rossoctl:mainfrom
varshaprasad96:fix/security-audit-remediations

Conversation

@varshaprasad96

@varshaprasad96 varshaprasad96 commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Summary

  • tokenbroker RFC 6750 compliance: replaced case-sensitive local extractBearer with canonical auth.ExtractBearer (case-insensitive per RFC 6750 §2.1)
  • Config redaction: new authlib/redact package strips client_secret, judge_bearer, passwords, and token values from /config and /v1/pipeline endpoints
  • Bypass path validation: bypass.NewMatcher now rejects overly broad patterns (*, /*, empty) and trims whitespace from patterns before storing
  • Cache eviction: replaced full-clear with random-sample eviction (~25%) to prevent cache-miss storms under sustained load
  • extproc RunFinish: RunFinish is now called on rejected requests in all four ext_proc handler paths, honoring the pipeline Finisher contract
  • statserver fixes: preserved application/json Content-Type on error path, restored trailing newline consistency with /stats

Context

A security audit of opendatahub-io/agents-operator (commit b6489069) produced 85 must-fix findings (8 CRITICAL, 77 HIGH). Since authbridge originated in this repo, many findings apply here. This PR addresses the verified, fixable issues. False positives (TLS InsecureSkipVerify with custom VerifyPeerCertificate, $EDITOR exec pattern in abctl, SSE client timeout, init container USER root, pre-existing shellcheck disables) were triaged out.

Test plan

  • go vet ./... passes (verified locally)
  • go test ./... passes — all 36 packages green (verified locally)
  • New tests: TestNewMatcher_RejectsFootgunPatterns, TestNewMatcher_TrimsWhitespace, TestMaxSize_RandomEviction, TestJSON_* (8 redaction tests including nested arrays)
  • Updated tests: TestExtractBearer_EdgeCases (now RFC 6750 case-insensitive), TestTokenBroker_OnRequest_DifferentAuthSchemes (lowercase bearer accepted)

Singed-off-by: Varsha Prasad Narsing varshaprasad96@gmail.com
Assisted-By: Claude (Anthropic AI) noreply@anthropic.com

Summary by CodeRabbit

  • New Features

    • Automatic redaction of sensitive configuration and JSON payloads in API responses
    • Added redaction utility for sanitizing nested JSON values
  • Bug Fixes

    • Pattern validation now rejects overly broad bypass patterns
    • Pipeline finish hooks consistently run when requests are rejected
  • Refactor

    • Bearer token extraction consolidated to a shared implementation
  • Tests

    • Expanded test coverage for redaction, matcher validation, cache eviction, and bearer extraction behaviors

Address HIGH/CRITICAL findings from the agents-operator security audit
that apply to the authbridge code in this repo:

- tokenbroker: replace case-sensitive extractBearer with canonical
  auth.ExtractBearer (RFC 6750 compliance)
- bypass: reject overly broad patterns ("*", "/*", "") and trim
  whitespace from patterns before storing
- redact: new package to strip sensitive fields (client_secret,
  judge_bearer, passwords, tokens) from JSON config payloads
- statserver: redact /config endpoint output, fix Content-Type on
  error responses, restore trailing newline consistency
- sessionapi: redact /v1/pipeline plugin config output
- cache: replace full-clear eviction with random-sample eviction
  (~25%) to prevent cache-miss storms under pressure
- extproc: call RunFinish on rejected requests so pipeline Finisher
  contract is honored even on deny paths

Signed-off-by: Varsha Prasad Narsing <vnarsing@redhat.com>
Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Varsha Prasad Narsing <varshaprasad96@gmail.com>
@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: defe8838-c17f-473a-8b15-4f5468650ff3

📥 Commits

Reviewing files that changed from the base of the PR and between d9e632b and 8edd421.

📒 Files selected for processing (3)
  • authbridge/authlib/plugins/tokenbroker/plugin.go
  • authbridge/authlib/redact/redact.go
  • authbridge/authlib/redact/redact_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • authbridge/authlib/plugins/tokenbroker/plugin.go
  • authbridge/authlib/redact/redact_test.go

📝 Walkthrough

Walkthrough

Consolidates and hardens authlib: adds JSON redaction for sensitive fields, tightens bypass pattern validation, improves cache eviction, ensures pipeline finish hooks run on early rejection, and centralizes bearer extraction to a shared utility.

Changes

Authlib Enhancements

Layer / File(s) Summary
JSON redaction package
authbridge/authlib/redact/redact.go, authbridge/authlib/redact/redact_test.go
New redact.JSON() recursively redacts sensitive JSON string fields by suffix (secret, password, token, bearer, key, credential) with tests for nested objects/arrays, non-objects, empty/nil input, and non-string preservation.
Bypass pattern validation
authbridge/authlib/bypass/matcher.go, authbridge/authlib/bypass/matcher_test.go
NewMatcher trims patterns, validates path.Match syntax, rejects empty or match-all-like patterns (*, /*), and stores cleaned patterns. Tests cover footgun rejection, specific wildcard allowance, and trimming.
Cache eviction strategy
authbridge/authlib/plugins/tokenexchange/cache/cache.go, authbridge/authlib/plugins/tokenexchange/cache/cache_test.go
When Set() would exceed maxSize, expired entries are evicted first; if still full, evictRandom() deletes entries until size ≈75% of maxSize. Test asserts probabilistic retention and overflow-entry presence.
Redaction integration in config APIs
authbridge/authlib/observe/statserver.go, authbridge/authlib/sessionapi/server.go
Stat server marshals, redacts, and writes config JSON (with trailing newline); session API redacts plugin raw config (pipeline.RawConfigProvider) before inclusion in /v1/pipeline response.
Pipeline lifecycle on rejection
authbridge/authlib/listener/extproc/server.go
handleInbound, handleInboundBody, handleOutbound, and handleOutboundBody now call RunFinish on the pipeline context immediately when recording a reject before returning Envoy ImmediateResponse.
Bearer extraction consolidation
authbridge/authlib/plugins/tokenbroker/plugin.go, authbridge/authlib/plugins/tokenbroker/plugin_edge_test.go
Token broker uses shared auth.ExtractBearer() (local helper removed). Tests updated to call auth.ExtractBearer, accept lowercase bearer, and assert whitespace-preserving token behavior.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • huang195
  • pdettori
  • mrsabath

Poem

🐰 In code I nibble, trim, and bind,

Secrets tucked where eyes won't find.
Cache hops light, evicting some,
Pipelines finish, tasks undone.
Tokens shared, extraction neat—happy rabbit treats the feat.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main purpose of the pull request—remediating security audit findings in authbridge across multiple components.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
authbridge/authlib/redact/redact_test.go (1)

38-52: ⚡ Quick win

Add a regression case for sensitive keys that wrap objects or arrays.

The suite only exercises sensitive keys at string leaves. It doesn’t cover the currently broken shape where the sensitive key itself owns a container, e.g. {"token":{"access_token":"..."}}, so this leak can slip through without a failing test.

Also applies to: 103-119

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@authbridge/authlib/redact/redact_test.go` around lines 38 - 52, Test suite
only checks redaction when sensitive keys map to string leaves; add regression
cases in redact_test.go that exercise sensitive keys whose values are objects
and arrays (e.g., {"token": {"access_token":"..."}} and
{"token":[{"access_token":"..."}]}) to ensure JSON(in) / JSON() redacts the
entire value to "[REDACTED]". Modify or add tests (e.g., extend
TestJSON_RedactsNestedKeys or add new TestJSON_RedactsContainerValues) to
unmarshal JSON(out) and assert that the sensitive key (e.g., "token",
"jwt_password" if used as a container in tests) equals "[REDACTED]" and that
other sibling keys remain unchanged.
authbridge/authlib/plugins/tokenexchange/cache/cache_test.go (1)

68-90: 💤 Low value

Solid test coverage for random eviction behavior.

The test correctly validates:

  1. Cache size falls to ~75% capacity (target=75) + the new overflow entry
  2. The overflow entry is present after eviction (critical check)

The margin of 70-80 appropriately accounts for the non-deterministic map iteration order.

Optional readability suggestion:

Consider using `fmt.Sprintf` for clearer test keys

Line 72 uses string(rune(i)) to generate unique keys, which produces Unicode characters U+0000 through U+0063. While this works correctly, it's less readable than:

+import "fmt"
+
 func TestMaxSize_RandomEviction(t *testing.T) {
 	const maxSize = 100
 	c := New(WithMaxSize(maxSize))
 	for i := range maxSize {
-		c.Set("token"+string(rune(i)), "aud", "val", 5*time.Minute)
+		c.Set(fmt.Sprintf("token%d", i), "aud", "val", 5*time.Minute)
 	}

The current approach avoids the fmt import, so this is purely a style preference.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@authbridge/authlib/plugins/tokenexchange/cache/cache_test.go` around lines 68
- 90, In TestMaxSize_RandomEviction replace the key generation using
string(rune(i)) with a clearer formatted string (e.g. fmt.Sprintf("%d", i)) when
calling c.Set inside the loop so test keys are readable and unambiguous; update
imports to include fmt. Locate the loop that invokes
c.Set("token"+string(rune(i)), "aud", "val", 5*time.Minute) and change the
concatenation to use a formatted integer key (e.g. "token"+fmt.Sprintf("%d", i))
and add fmt to the test file imports.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@authbridge/authlib/redact/redact.go`:
- Around line 36-40: The loop currently short-circuits on sensitive keys
(isSensitiveKey(k)) and only replaces direct string values, then continues,
which skips recursion into container-valued sensitive fields and leaks nested
secrets; change the branch in the redact/map-walking logic so that when
isSensitiveKey(k) is true you still redact if v is a string (m[k] =
"[REDACTED]") but do NOT "continue" for non-string values — instead detect
container types (map[string]interface{} and []interface{}) and recursively call
the existing redact helper(s) (e.g., redactValue/redactMap/redactSlice or
whatever recursion functions exist) to descend and redact nested entries,
leaving other primitive non-string types untouched. Ensure you reference the
same symbols used in the file (isSensitiveKey, the map iteration block, and the
recursive redact helpers) so callers behave correctly for payloads like
{"token": {"access_token":"secret"}}.

---

Nitpick comments:
In `@authbridge/authlib/plugins/tokenexchange/cache/cache_test.go`:
- Around line 68-90: In TestMaxSize_RandomEviction replace the key generation
using string(rune(i)) with a clearer formatted string (e.g. fmt.Sprintf("%d",
i)) when calling c.Set inside the loop so test keys are readable and
unambiguous; update imports to include fmt. Locate the loop that invokes
c.Set("token"+string(rune(i)), "aud", "val", 5*time.Minute) and change the
concatenation to use a formatted integer key (e.g. "token"+fmt.Sprintf("%d", i))
and add fmt to the test file imports.

In `@authbridge/authlib/redact/redact_test.go`:
- Around line 38-52: Test suite only checks redaction when sensitive keys map to
string leaves; add regression cases in redact_test.go that exercise sensitive
keys whose values are objects and arrays (e.g., {"token":
{"access_token":"..."}} and {"token":[{"access_token":"..."}]}) to ensure
JSON(in) / JSON() redacts the entire value to "[REDACTED]". Modify or add tests
(e.g., extend TestJSON_RedactsNestedKeys or add new
TestJSON_RedactsContainerValues) to unmarshal JSON(out) and assert that the
sensitive key (e.g., "token", "jwt_password" if used as a container in tests)
equals "[REDACTED]" and that other sibling keys remain unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fb6c31ba-455b-47d5-b94c-a51a58f4241d

📥 Commits

Reviewing files that changed from the base of the PR and between f1c5e76 and d9e632b.

📒 Files selected for processing (11)
  • authbridge/authlib/bypass/matcher.go
  • authbridge/authlib/bypass/matcher_test.go
  • authbridge/authlib/listener/extproc/server.go
  • authbridge/authlib/observe/statserver.go
  • authbridge/authlib/plugins/tokenbroker/plugin.go
  • authbridge/authlib/plugins/tokenbroker/plugin_edge_test.go
  • authbridge/authlib/plugins/tokenexchange/cache/cache.go
  • authbridge/authlib/plugins/tokenexchange/cache/cache_test.go
  • authbridge/authlib/redact/redact.go
  • authbridge/authlib/redact/redact_test.go
  • authbridge/authlib/sessionapi/server.go

Comment thread authbridge/authlib/redact/redact.go Outdated
@huang195 huang195 changed the title fix: remediate security audit findings in authbridge Fix: remediate security audit findings in authbridge Jun 12, 2026

@huang195 huang195 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

Solid, well-scoped remediation of the audit findings, with good test coverage across the board (footgun-pattern rejection, whitespace trim, random-eviction bounds, 8 redaction cases incl. nested arrays, RFC-6750 bearer edge cases). A few notes:

  • Genuine wins: redacting the unauthenticated session API + /config is a real improvement (closes a known info-exposure gap); RunFinish-on-reject is applied symmetrically across all four ext_proc paths (honors the Finisher contract — worth a quick confirm that finishers are idempotent and the Outcome reflects "rejected"); the statserver error path correctly preserves application/json since Content-Type is set before WriteHeader.
  • Bypass matcher: rejecting *//*/empty is good hardening, but note it's a denylist (a pattern like /v1/* is still broad yet allowed) and it's a behavior change — any existing deployment with bypass_paths: ["*"] or ["/*"] will now fail at NewMatcher (fail-closed, which is the intent, but worth a release-note line).
  • Cache eviction (nit): evictRandom relies on Go's map-iteration order, which is randomized but bucket-biased (not uniform) and doesn't prefer expired/LRU entries — fine for breaking the full-clear storm, just not truly "random."

The two inline suggestions (redaction key coverage; bearer trim semantics) are the only things I'd act on, neither blocking.

Areas reviewed: Go (bypass, redact, cache, extproc, statserver, tokenbroker, sessionapi) + tests; cross-checked the auth.ExtractBearer impl · Commits: 1, signed-off ✅ (the PR body has a "Singed-off-by" typo, but the commit trailer is correct — DCO passed) · CI: green

No blockers — approving.

Assisted-By: Claude Code

Comment thread authbridge/authlib/redact/redact.go Outdated
Comment thread authbridge/authlib/plugins/tokenbroker/plugin.go Outdated
@huang195

Copy link
Copy Markdown
Member

@varshaprasad96 I'll hold off merging in case you want to make any of the suggested changes. We can also merge as is as these are not must-fix.

- redact: add "key" and "credential" to sensitive suffix list for
  defense-in-depth (api_key, private_key, signing_key, etc.)
- redact: descend into container-valued sensitive keys instead of
  skipping them — a field like {"token": {"access_token": "..."}}
  now has its nested secrets redacted
- redact: add best-effort doc noting inline secrets should use
  *_file paths; this layer is defense-in-depth
- tokenbroker: TrimSpace the extracted bearer token so whitespace-only
  tokens ("Bearer   ") are treated as missing rather than forwarded
  to the broker

Signed-off-by: Varsha Prasad Narsing <vnarsing@redhat.com>
Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Varsha Prasad Narsing <varshaprasad96@gmail.com>
@varshaprasad96

Copy link
Copy Markdown
Contributor Author

@huang195 addressed the changes suggested in the review. Thanks!

@huang195
huang195 merged commit 2fce417 into rossoctl:main Jun 12, 2026
21 checks passed
@github-project-automation github-project-automation Bot moved this from New /:ToDo to Done in Rossoctl Issue Prioritization Jun 12, 2026
kellyaa added a commit to kellyaa/kagenti-extensions that referenced this pull request Jun 12, 2026
Adds construction-time guards, audit logging, and tests asked for in
the PR rossoctl#500 review.

Skiphost matcher (authlib/listener/skiphost):
- Reject match-all patterns ("*", "**", whitespace-only) at New() so a
  single misconfigured entry cannot silently disable the entire
  outbound enforcement pipeline. Mirrors the bypass-pattern guard
  added to ibac in rossoctl#496. Empirically verified: under .-delimited
  glob "*" matches every single-label hostname (which is how every
  short Kubernetes service name reaches the listener), and "**" is
  the unambiguous match-all. Patterns like "*.svc.cluster.local" or
  "*.*" are NOT match-all and remain accepted.
- Reject patterns containing ":" so an operator typo like
  "otel-collector:8335" fails fast at startup rather than silently
  never matching (Match strips the port from the incoming host
  before comparing, so colon-bearing patterns would never fire).
- Add MatchPattern(host) (string, bool) so listeners can attribute
  audit logs / counters to the specific raw operator-supplied
  pattern that fired. Match() is now a thin wrapper around it for
  callers that only want the boolean.

Listener skip-path audit logging:
- Each successful skip in extproc (handleOutbound, handleOutboundBody)
  and forwardproxy (handleRequest, handleConnect) now emits a
  structured slog INFO line carrying host, matched pattern, and
  method/path. Without this trail, a successful self-exemption left
  no trace anywhere -- no session event, no plugin invocation --
  because that is exactly what skip means.

Trust-model documentation (authlib/config/config.go):
- ListenerConfig.SkipHosts godoc now spells out that the matched
  value is agent-influenceable on the ext_proc and HTTP-forward
  paths (Envoy :authority / r.Host header) and only safe-by-
  construction on the CONNECT-tunnel path. Operators should not
  list a host they would want IBAC / token-exchange to deny on.

Hard-guard reminder (authlib/listener/forwardproxy/transparent.go):
- HandleTransparentConn (proxy-sidecar enforce-redirect mode)
  intentionally does NOT consult SkipHosts. Add a paragraph to its
  godoc stating the omission is deliberate so a future maintainer
  does not "fix the inconsistency" by adding a skip keyed on the
  agent-controlled SNI/Host bytes and open a real bypass of the
  hard egress guard.

Tests:
- New TestForwardProxy_SkipHosts_MatchesAgainstHostHeaderNotURL
  locks the trust-model contract for the HTTP forward path: the
  skip keys on the agent-supplied Host header, not on r.URL.Host.
- New TestForwardProxy_SkipHosts_CONNECT_BypassesPipeline exercises
  the previously-uncovered CONNECT skip path end-to-end.
- New TestNew_RejectsMatchAllStar / RejectsMatchAllDoubleStar /
  RejectsWhitespaceOnly / RejectsPortInPattern lock the construction-
  time guards, plus TestNew_AcceptsLeadingStar guards against a
  future rewrite that over-rejects and breaks operator-typical FQDN
  patterns.

CLAUDE.md gotcha rossoctl#11:
- Narrow the LastIntent() wording: the pin protects against FIFO
  eviction only; expired or deleted sessions can still return nil.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Kelly Abuelsaad <kaymar@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants