Skip to content

fix: address CodeRabbit review findings for PR #419#421

Merged
fredcamaral merged 9 commits intodevelopfrom
fix/coderabbit-autofix-pr419
Mar 30, 2026
Merged

fix: address CodeRabbit review findings for PR #419#421
fredcamaral merged 9 commits intodevelopfrom
fix/coderabbit-autofix-pr419

Conversation

@fredcamaral
Copy link
Copy Markdown
Member

Summary

Key changes:

  • certificate: Defensive copies in GetCertificate/TLSCertificate, nil-receiver safety, expanded godocs
  • systemplane: Nil-safety guards for setting helpers, consolidated state loads, duplicate env var detection in catalog
  • webhook: URL userinfo redaction, versioned HMAC signatures (v0/v1 backward-compatible), DNS-free tests, authority/SNI split
  • dlq: Clone sources slice, reject negative MaxRetries, validate tenant key segments, refactored Enqueue complexity
  • idempotency: Binary-safe cached responses with headers, fail-open error handling, accurate package docs

Test plan

  • make ci — full local CI pipeline passes
  • All 6,371 unit tests pass
  • All 60 integration tests pass
  • Lint clean (0 issues, gocyclo within limits)
  • CodeRabbit re-review after merge

Address 21 unresolved CodeRabbit review findings across certificate,
systemplane, webhook, DLQ, and idempotency packages:

- certificate: defensive copies in GetCertificate/TLSCertificate, nil-receiver
  safety for TLSCertificate, expanded DaysUntilExpiry godoc
- systemplane: nil-safety guards for SnapSettingInt/SnapSettingBool,
  consolidate redundant state loads in supervisor reload, robust factory
  assertion in backend test, duplicate env var detection in catalog validation
- webhook: redact URL userinfo in logs, versioned HMAC signature format (v1)
  with backward-compatible migration path, DNS-free scheme validation tests,
  preserve original authority for HTTP Host header, defensive filter comment,
  consistent metric reporting with result.Attempts
- dlq: clone sources slice in WithSources, reject negative MaxRetries,
  validate tenant key segment before Redis routing
- idempotency: binary-safe cached response with headers, fail-open error
  handling in handleDuplicate, accurate package documentation
- Extract validateEnqueueMessage, stampInitialEnqueue, and
  resolveAndValidateTenant helpers from Handler.Enqueue to bring
  cyclomatic complexity from 18 down to 7
- Add missing "errors" import in idempotency package for errors.Is calls
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 30, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This change set touches many commons packages. Notable factual changes: certificate Manager returns deep-cloned x509.Certificate objects; DLQ consumer clones source slices; DLQ Handler splits enqueue into validation, stamping, and tenant-resolution helpers; idempotency middleware now stores response bodies as bytes, persists selected headers, adds Redis fail-open reads and header replay; a new commons/security/ssrf package implements SSRF validation and DNS pinning and callers delegate to it; webhook deliverer adds versioned HMAC (v0/v1) and verification helpers plus hardened URL sanitization; catalog validation now returns ValidationResult including EnvVarConflicts; assorted tests and docs updated.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant IdemMiddleware as Idempotency Middleware
    participant Redis
    participant Handler as Downstream Handler

    Client->>IdemMiddleware: Request (may include X-Idempotency-Key)
    activate IdemMiddleware

    IdemMiddleware->>Redis: GET lock/key
    activate Redis
    Redis-->>IdemMiddleware: lock status or error
    deactivate Redis

    alt Redis read error (fail-open)
        IdemMiddleware->>Handler: Invoke handler (bypass replay)
        activate Handler
        Handler-->>IdemMiddleware: Response (status, body, headers)
        deactivate Handler
        IdemMiddleware-->>Client: Response
    else Lock indicates in-flight
        IdemMiddleware-->>Client: 409 (IDEMPOTENCY_CONFLICT)
    else Lock free / no cached response
        IdemMiddleware->>Handler: Invoke handler
        activate Handler
        Handler-->>IdemMiddleware: Response (status, body, headers)
        deactivate Handler

        alt Success and body size within max cache
            IdemMiddleware->>IdemMiddleware: Capture body ([]byte) and selected headers
            IdemMiddleware->>Redis: PIPELINE SET key:response, SET lock expiry
            activate Redis
            Redis-->>IdemMiddleware: OK
            deactivate Redis
            IdemMiddleware-->>Client: Response (replayable)
        else Failure
            IdemMiddleware->>Redis: DELETE key & lock
            activate Redis
            Redis-->>IdemMiddleware: OK
            deactivate Redis
            IdemMiddleware-->>Client: Error
        end
    end

    deactivate IdemMiddleware
Loading
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'fix: address CodeRabbit review findings for PR #419' directly describes the main objective of this PR—addressing review findings from a prior PR. It is concise, specific enough to understand the primary action, and clearly summarizes the central change across multiple packages.
Description check ✅ Passed The PR description provides a comprehensive summary including affected areas (5 packages), key changes with bullets, a test plan with checkmarks, and a commit history note. It covers the required elements expected in a mature PR: what was changed, why, and testing verification. The description is substantive and directly addresses the template checklist items.

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


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

staticcheck SA1019: fasthttp Header.VisitAll is deprecated in favor of
the range-compatible All() iterator.
@fredcamaral
Copy link
Copy Markdown
Member Author

@CodeRabbit -- review again, please

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@commons/net/http/idempotency/idempotency.go`:
- Around line 254-260: The replay loop currently uses c.Set(name, v) which
overwrites previous values and drops multi-value headers; update the loop that
iterates over resp.Headers to append each value instead of setting—replace the
c.Set(name, v) call with a call that adds the header (e.g.,
c.Response().Header.Add(name, v)) so headers like Set-Cookie, Location, ETag,
etc. are preserved across multiple values when replaying responses.
- Around line 252-267: The json.Unmarshal failure for variable resp
(cachedResponse) is currently ignored; update the idempotency replay path in
idempotency.go to log a warning including the unmarshal error and the cached
payload key/identifier before falling through so we preserve fail-open
semantics—locate the block that unmarshals into resp and add a processLogger or
package logger call (including the error and relevant cache key/ctx info)
immediately when unmarshalErr != nil, then continue to the existing fallback
that returns the generic "already processed" response.
- Around line 308-319: Replace the deprecated VisitAll call with iterating over
c.Response().Header.All() and append values exactly like before: call pairs :=
c.Response().Header.All() and loop over the slice in steps of two (key :=
pairs[i], value := pairs[i+1]), convert key to string to switch on header names
("Content-Type", "Content-Length", "Transfer-Encoding",
chttp.IdempotencyReplayed") and append string(value) into headers[name] when not
skipped; update the code around c.Response().Header.VisitAll to use this All()
iteration pattern so types match and behavior remains identical.

In `@commons/systemplane/catalog/validate.go`:
- Around line 170-177: The current logic keeps the first mapping in envIndex
when a duplicate env var is found, causing resolveSharedKey to incorrectly match
ambiguous env vars; update the duplicate handling in the duplicate-detection
block (where envIndex, EnvVarConflict and sk.Key are used) to remove the env var
from envIndex (or record it in a separate ambiguous set) immediately when you
append the EnvVarConflict so that future resolveSharedKey lookups will not
return the original mapping for that env var and will treat it as ambiguous.
- Around line 123-130: The loop that appends envConflicts is misusing the
Mismatch type (in the for _, conflict := range envConflicts block), inserting
catalog-vs-catalog keys into ProductKey/ProductValue and bypassing vc.shouldSkip
filtering; change this by either creating a dedicated result type (e.g.,
EnvVarDuplicate or CatalogConflict) and appending that instead of Mismatch, or
by passing each conflict through the existing mismatch-generation path that
calls vc.shouldSkip/WithIgnoreFields("EnvVar") before appending so filtering
behavior and Mismatch.String() remain correct; update the code that consumes
these results to handle the new type if you add one, or reuse the same Mismatch
creation function so existing callers/filters continue to work.

In `@commons/webhook/deliverer_test.go`:
- Around line 958-976: The test TestComputeHMACv1 currently only checks prefix
and determinism so it can pass even if the internal wire format is wrong; update
it to assert against a known, independently computed v1 signature string for the
chosen payload/timestamp/secret (use computeHMACv1 inputs: payload :=
[]byte(`{"id":"123"}`), timestamp := int64(1700000000), secret :=
"test-secret")—replace or add an assertion that sig ==
"<externally-computed-expected-signature>" to lock the exact "v1,sha256=<hex>" /
format behavior of computeHMACv1 and fail if the timestamp/payload concatenation
or encoding differs.

In `@commons/webhook/deliverer.go`:
- Around line 147-155: The godoc for WithSignatureVersion incorrectly references
VerifySignatureV1; update the comment to point to the actual exported verifier
helpers VerifySignature and VerifySignatureWithFreshness and clarify that
SignatureV1 produces the versioned "v1,sha256=..." signature string enabling
replay protection which receivers can enforce using VerifySignatureWithFreshness
(or VerifySignature for basic verification). Edit the paragraph mentioning
SignatureV1 to reference those two functions (VerifySignature,
VerifySignatureWithFreshness) and adjust phrasing to indicate which verifier
provides freshness/replay protection.
- Around line 741-756: The sanitizeURL function currently clears query and
userinfo but leaves URL fragments, so sensitive data in fragments (e.g., access
tokens) can leak via u.String(); update sanitizeURL to also clear the fragment
by setting u.Fragment = "" before returning, and add/adjust the TestSanitizeURL
test case to assert that fragments are removed (e.g., include a URL with
`#fragment` and expect the placeholder-free sanitized result without the
fragment).
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4f2c383b-9b08-4b5b-b5bd-c2c90c8b6dc5

📥 Commits

Reviewing files that changed from the base of the PR and between 7b684cf and ce56bc2.

📒 Files selected for processing (16)
  • commons/certificate/certificate.go
  • commons/certificate/certificate_test.go
  • commons/dlq/consumer.go
  • commons/dlq/handler.go
  • commons/net/http/idempotency/doc.go
  • commons/net/http/idempotency/idempotency.go
  • commons/systemplane/bootstrap/backend_test.go
  • commons/systemplane/catalog/validate.go
  • commons/systemplane/domain/setting_helpers.go
  • commons/systemplane/service/supervisor.go
  • commons/systemplane/service/supervisor_helpers.go
  • commons/webhook/deliverer.go
  • commons/webhook/deliverer_test.go
  • commons/webhook/doc.go
  • commons/webhook/ssrf.go
  • commons/webhook/ssrf_test.go

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 30, 2026

@fredcamaral Sure! Kicking off a fresh review of PR #421 now. 🐰

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
commons/net/http/idempotency/idempotency.go (2)

254-260: ⚠️ Potential issue | 🟠 Major

Multi-value headers are silently overwritten during replay.

c.Set(name, v) overwrites any previous value for the same header name. For headers that legitimately have multiple values (e.g., Set-Cookie), only the last value will be replayed.

Use c.Response().Header.Add() to append values:

🐛 Proposed fix
 			for name, values := range resp.Headers {
 				for _, v := range values {
-					c.Set(name, v)
+					c.Response().Header.Add(name, v)
 				}
 			}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@commons/net/http/idempotency/idempotency.go` around lines 254 - 260, The loop
that replays persisted headers (for name, values := range resp.Headers { ... })
uses c.Set(name, v), which overwrites previous values and drops multi-value
headers like Set-Cookie; change the body to append each value instead by calling
the response header Add method (e.g., c.Response().Header.Add(name, v)) so all
values are preserved when replaying headers in idempotency.go.

252-253: ⚠️ Potential issue | 🟡 Minor

Silent unmarshal failure masks cache corruption.

When json.Unmarshal fails (line 253), the error is silently ignored and the request falls through to the generic "already processed" response. This could mask data corruption or schema evolution issues without any observability.

Consider logging a warning to aid debugging while preserving fail-open semantics:

📝 Proposed fix
 		var resp cachedResponse
-		if unmarshalErr := json.Unmarshal([]byte(cached), &resp); unmarshalErr == nil {
+		if unmarshalErr := json.Unmarshal([]byte(cached), &resp); unmarshalErr != nil {
+			m.logger.Log(ctx, log.LevelWarn,
+				"idempotency: failed to unmarshal cached response, treating as unavailable",
+				log.String("key", responseKey), log.Err(unmarshalErr),
+			)
+		} else {
 			// Replay persisted headers first...

Based on learnings: "Do not swallow errors; return or handle errors with context"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@commons/net/http/idempotency/idempotency.go` around lines 252 - 253, When
json.Unmarshal into cachedResponse (the variable resp) fails for the cached
string (variable cached), don’t silently ignore it: add a warning log that
includes the unmarshal error and relevant context (e.g., the cached payload or
its key) before preserving the current fail-open behavior; update the error
branch of the json.Unmarshal check (the else path where unmarshalErr != nil) to
call the package logger (or log.Printf/log.Warnf) with the error and cached
content so cache corruption or schema drift is observable while still returning
the existing "already processed" behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@commons/net/http/idempotency/idempotency.go`:
- Around line 308-319: The loop variable named `key` inside the response header
iteration shadows the function parameter `key`; rename the loop variable (e.g.,
to `hdrKey` or `headerName`) wherever it appears in the for-range over
c.Response().Header.All() — update the usages string(key) and switch on name
accordingly — so the function parameter `key` is not shadowed and readability is
improved (references: the for-range over c.Response().Header.All(), the switch
on name, and the headers[name] = append(...) line).

---

Duplicate comments:
In `@commons/net/http/idempotency/idempotency.go`:
- Around line 254-260: The loop that replays persisted headers (for name, values
:= range resp.Headers { ... }) uses c.Set(name, v), which overwrites previous
values and drops multi-value headers like Set-Cookie; change the body to append
each value instead by calling the response header Add method (e.g.,
c.Response().Header.Add(name, v)) so all values are preserved when replaying
headers in idempotency.go.
- Around line 252-253: When json.Unmarshal into cachedResponse (the variable
resp) fails for the cached string (variable cached), don’t silently ignore it:
add a warning log that includes the unmarshal error and relevant context (e.g.,
the cached payload or its key) before preserving the current fail-open behavior;
update the error branch of the json.Unmarshal check (the else path where
unmarshalErr != nil) to call the package logger (or log.Printf/log.Warnf) with
the error and cached content so cache corruption or schema drift is observable
while still returning the existing "already processed" behavior.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: af52c639-968e-46fc-8e18-a63a2a9e7297

📥 Commits

Reviewing files that changed from the base of the PR and between ce56bc2 and f63d6e4.

📒 Files selected for processing (1)
  • commons/net/http/idempotency/idempotency.go

- idempotency: use Header.Add for multi-value replay, log unmarshal
  failures, rename shadowed loop variable
- catalog: introduce ValidationResult to separate env var conflicts from
  Mismatch, mark duplicate env vars as ambiguous in index
- webhook: strip URL fragments in sanitizeURL, fix godoc references to
  VerifySignature/VerifySignatureWithFreshness, pin TestComputeHMACv1
  to independently computed expected value
@fredcamaral
Copy link
Copy Markdown
Member Author

Round 2 — Fixes Applied Successfully

Fixed 5 file(s) based on 8 unresolved review comment(s).

Files modified:

  • commons/net/http/idempotency/idempotency.go
  • commons/systemplane/catalog/validate.go
  • commons/systemplane/catalog/validate_options_test.go
  • commons/webhook/deliverer.go
  • commons/webhook/deliverer_test.go

Commit: 90b2e68

All 6,373 unit tests pass. Lint clean (0 issues).

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
commons/systemplane/catalog/validate_options_test.go (1)

101-136: 🧹 Nitpick | 🔵 Trivial

Add tests for EnvVarConflicts detection and handling.

The EnvVarConflict feature in buildCatalogIndexes detects and tracks duplicate env var mappings but lacks dedicated test coverage. This includes verification of:

  • Detection of the first duplicate env var mapping (when a duplicate is found in envIndex)
  • Handling of subsequent conflicts for already-ambiguous env vars (when ambiguousEnvVars is set)
  • Confirmation that ambiguous env vars are excluded from envIndex so they don't resolve during validation

Current tests in validate_test.go manually check for duplicates via TestAllSharedKeys_NoDuplicateEnvVars but do not verify the returned result.EnvVarConflicts field itself. Tests in validate_options_test.go focus only on Mismatches.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@commons/systemplane/catalog/validate_options_test.go` around lines 101 - 136,
Tests are missing coverage for EnvVarConflicts produced by buildCatalogIndexes:
add unit tests that call buildCatalogIndexes (or indirectly via
ValidateKeyDefsWithOptions) with shared key sets that create duplicate EnvVar
entries and assert the returned result.EnvVarConflicts contains the first
detected conflict, that further duplicates are tracked as ambiguousEnvVars
(i.e., subsequent conflicts mark the same env var as ambiguous), and that
ambiguous env vars are not present in the returned envIndex so they cannot
resolve during validation; reference functions/types: buildCatalogIndexes,
EnvVarConflicts, envIndex, ambiguousEnvVars, ValidateKeyDefsWithOptions, and add
equivalents to the tests in validate_options_test.go to assert
result.EnvVarConflicts and absence from envIndex.
♻️ Duplicate comments (1)
commons/net/http/idempotency/idempotency.go (1)

315-329: ⚠️ Potential issue | 🟠 Major

The multi-value header fix is still lossy on the write side.

This loop appends one serialized value per header name, so repeated headers can already be flattened before they reach resp.Headers. Header.Add on replay won't recover separate Set-Cookie lines once they're collapsed here; capture the response with an API that yields every emitted header occurrence. A regression test with two Set-Cookie values would make this path explicit.

In the Fiber/fasthttp version used by this repository, what API should be used to enumerate every response header occurrence—especially repeated Set-Cookie headers—without flattening them into a single value per header name?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@commons/net/http/idempotency/idempotency.go` around lines 315 - 329, The
current loop uses c.Response().Header.All() which flattens repeated headers;
instead iterate every emitted header occurrence via c.Response().Header.VisitAll
(or Header.VisitAll for fasthttp) to capture each key/value pair separately (so
duplicate Set-Cookie entries are preserved), skip the same middleware-managed
names (Content-Type, Content-Length, Transfer-Encoding,
chthtp.IdempotencyReplayed), and append each visited value to the headers map;
add a regression test that returns two Set-Cookie headers to assert both are
recorded and replayed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@commons/net/http/idempotency/idempotency.go`:
- Around line 228-250: The code treats a missing Redis key (errors.Is(...,
redis.Nil)) as if the operation is complete and later may return a cached 200
even when no completion marker exists; update the logic around client.Get(ctx,
key).Result() and client.Get(ctx, responseKey).Result() so that when Get returns
redis.Nil after a lost SetNX race you do NOT consider the request "complete" —
instead re-enter the acquisition/retry path or fail open (call c.Next()) rather
than mapping to a successful cached response; adjust handling in the blocks
around keyValue/keyErr and cached/cacheErr (and the analogous block at 280-296)
to explicitly detect redis.Nil and either retry acquiring the idempotency marker
or return c.Next() rather than treating it as a completed response.
- Around line 231-234: The WARN logs currently pass the raw idempotency key into
m.logger.Log (e.g., the calls that include log.String("key", key) around
m.logger.Log), which leaks high-cardinality client/tenant data; replace those
raw key fields with a deterministic surrogate (e.g., compute a fast hash like
SHA-256 or HMAC of the key and log only a truncated hex of that hash or a
fixed-length prefix) and use that surrogate in the log.String("key", ...)
argument; apply the same change to the other occurrences mentioned (the
m.logger.Log calls around the ranges corresponding to lines 245-248 and 257-260)
so no raw idempotency key is emitted.

---

Outside diff comments:
In `@commons/systemplane/catalog/validate_options_test.go`:
- Around line 101-136: Tests are missing coverage for EnvVarConflicts produced
by buildCatalogIndexes: add unit tests that call buildCatalogIndexes (or
indirectly via ValidateKeyDefsWithOptions) with shared key sets that create
duplicate EnvVar entries and assert the returned result.EnvVarConflicts contains
the first detected conflict, that further duplicates are tracked as
ambiguousEnvVars (i.e., subsequent conflicts mark the same env var as
ambiguous), and that ambiguous env vars are not present in the returned envIndex
so they cannot resolve during validation; reference functions/types:
buildCatalogIndexes, EnvVarConflicts, envIndex, ambiguousEnvVars,
ValidateKeyDefsWithOptions, and add equivalents to the tests in
validate_options_test.go to assert result.EnvVarConflicts and absence from
envIndex.

---

Duplicate comments:
In `@commons/net/http/idempotency/idempotency.go`:
- Around line 315-329: The current loop uses c.Response().Header.All() which
flattens repeated headers; instead iterate every emitted header occurrence via
c.Response().Header.VisitAll (or Header.VisitAll for fasthttp) to capture each
key/value pair separately (so duplicate Set-Cookie entries are preserved), skip
the same middleware-managed names (Content-Type, Content-Length,
Transfer-Encoding, chthtp.IdempotencyReplayed), and append each visited value to
the headers map; add a regression test that returns two Set-Cookie headers to
assert both are recorded and replayed.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 816a5af3-8604-4e34-bb14-f5b68c9560cf

📥 Commits

Reviewing files that changed from the base of the PR and between f63d6e4 and 90b2e68.

📒 Files selected for processing (5)
  • commons/net/http/idempotency/idempotency.go
  • commons/systemplane/catalog/validate.go
  • commons/systemplane/catalog/validate_options_test.go
  • commons/webhook/deliverer.go
  • commons/webhook/deliverer_test.go

Introduce commons/security/ssrf as the single source of truth for SSRF
protection across all Lerian services. Consolidates two internal,
duplicated implementations into one exported package.

New exported API:
- IsBlockedIP(net.IP) / IsBlockedAddr(netip.Addr): IP-level blocking
  with canonical CIDR blocklist (8 ranges + stdlib predicates)
- IsBlockedHostname(hostname): hostname-level blocking for localhost,
  cloud metadata endpoints, .local/.internal/.cluster.local suffixes
- BlockedPrefixes(): returns copy of CIDR blocklist for auditing
- ValidateURL(ctx, url, opts...): scheme + hostname + IP validation
  without DNS resolution
- ResolveAndValidate(ctx, url, opts...): DNS-pinned validation with
  TOCTOU elimination, returns ResolveResult{PinnedURL, Authority,
  SNIHostname}
- Functional options: WithHTTPSOnly, WithAllowPrivateNetwork,
  WithLookupFunc, WithAllowHostname
- Sentinel errors: ErrBlocked, ErrInvalidURL, ErrDNSFailed

Refactored consumers:
- commons/webhook/ssrf.go: resolveAndValidateIP delegates to
  ssrf.ResolveAndValidate, removed duplicated isPrivateIP/CIDR blocklist
- commons/net/http/proxy_validation.go: isUnsafeIP delegates to
  ssrf.IsBlockedIP, removed duplicated blockedProxyPrefixes

Canonicalized on netip.Prefix (modern Go) with net.IP bridge for
legacy callers. All tests hermetic via WithLookupFunc injection.
Add repository shape entry, API invariants section, and other-packages
bullet for the new canonical SSRF validation package.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
commons/webhook/ssrf_test.go (1)

39-62: 🧹 Nitpick | 🔵 Trivial

Duplicate test coverage for scheme validation.

TestResolveAndValidateIP_UnsupportedSchemes (lines 39-62) and TestResolveAndValidateIP_InvalidScheme (lines 68-92) test overlapping schemes (gopher, file, ftp). Consider consolidating these into a single table-driven test to reduce redundancy.

Also applies to: 68-92

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@commons/webhook/ssrf_test.go` around lines 39 - 62, Consolidate the
overlapping scheme tests by merging the cases from
TestResolveAndValidateIP_UnsupportedSchemes and
TestResolveAndValidateIP_InvalidScheme into a single table-driven test (e.g.,
TestResolveAndValidateIP_InvalidOrUnsupportedSchemes) that runs t.Parallel and
iterates over all scheme cases, calls resolveAndValidateIP(context.Background(),
url), and asserts error is ErrSSRFBlocked; remove the duplicate test function to
avoid redundant coverage and keep references to the existing helper
resolveAndValidateIP and the ErrSSRFBlocked check so test behavior remains
identical.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@commons/security/ssrf/validate.go`:
- Around line 41-42: The ValidateURL function currently silences the unused ctx
via "_ = ctx" with a "reserved for future use" comment; replace that with a TODO
pointing to a tracking reference (e.g. TODO: integrate tracing/metrics -
ISSUE-1234) and keep ctx to preserve the API surface, or if you prefer to
acknowledge intent more explicitly, add a comment like "// TODO(OWNERS): use ctx
for tracing/metrics - ISSUE-1234" next to ctx usage; ensure the symbol
ValidateURL and parameter name ctx remain unchanged so callers/ABI are stable.
- Around line 126-147: The implementation currently fails fast on the first
blocked IP (see IsBlockedAddr check in the loop) which contradicts the godoc
that says "Validate ALL resolved IPs"; update the godoc (the comment near the
function in validate.go) to state that validation rejects the request if any
resolved IP is in a blocked range (fail-on-any-blocked-IP), referencing the
behavior of cfg.allowPrivate and IsBlockedAddr so the documentation matches the
actual logic.

---

Outside diff comments:
In `@commons/webhook/ssrf_test.go`:
- Around line 39-62: Consolidate the overlapping scheme tests by merging the
cases from TestResolveAndValidateIP_UnsupportedSchemes and
TestResolveAndValidateIP_InvalidScheme into a single table-driven test (e.g.,
TestResolveAndValidateIP_InvalidOrUnsupportedSchemes) that runs t.Parallel and
iterates over all scheme cases, calls resolveAndValidateIP(context.Background(),
url), and asserts error is ErrSSRFBlocked; remove the duplicate test function to
avoid redundant coverage and keep references to the existing helper
resolveAndValidateIP and the ErrSSRFBlocked check so test behavior remains
identical.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 50a8ea93-75b5-4d81-91af-80b60aa7b674

📥 Commits

Reviewing files that changed from the base of the PR and between 90b2e68 and 29eaa84.

📒 Files selected for processing (10)
  • AGENTS.md
  • commons/net/http/proxy_validation.go
  • commons/security/ssrf/doc.go
  • commons/security/ssrf/hostname.go
  • commons/security/ssrf/options.go
  • commons/security/ssrf/ssrf.go
  • commons/security/ssrf/ssrf_test.go
  • commons/security/ssrf/validate.go
  • commons/webhook/ssrf.go
  • commons/webhook/ssrf_test.go

Remove duplicate test functions (InvalidScheme was duplicate of BlockedSchemes, PrivateIP replaced by simpler BlockedHostname). Add dedicated TestMapSSRFError covering all four sentinel translation branches. Upgrade assert.Error to require.Error for fail-fast on nil errors.

X-Lerian-Ref: 0x1
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@commons/security/ssrf/ssrf.go`:
- Around line 61-84: IsBlockedAddr currently lets the zero-value netip.Addr{}
through; update IsBlockedAddr (and the similar block at lines 92-103) to
immediately treat invalid addresses as blocked by returning true when
addr.IsValid() is false (i.e., if !addr.IsValid() { return true }). Keep the
existing Unmap(), predicate checks, and blockedPrefixes loop unchanged so all
other logic still runs for valid addresses.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 62796042-05d9-4b6e-9abe-75985fa3e9df

📥 Commits

Reviewing files that changed from the base of the PR and between 29eaa84 and f35e341.

📒 Files selected for processing (2)
  • commons/security/ssrf/ssrf.go
  • commons/webhook/ssrf_test.go

@fredcamaral
Copy link
Copy Markdown
Member Author

Fixes Applied Successfully

Fixed 4 file(s) based on 6 unresolved review comment(s).

Issues addressed:

# Status Issue
1 ✅ Fixed Don't treat a vanished Redis marker as complete (CRITICAL)
2 ✅ Fixed Redact idempotency keys in WARN logs (MAJOR)
3 ⏩ Already fixed Strip URL fragments in sanitizeURL
4 ✅ Fixed Add validation check for invalid netip.Addr in IsBlockedAddr (MAJOR)
5 ✅ Fixed Clarify godoc: validation rejects on ANY blocked IP (MINOR)
6 ✅ Fixed Add TODO for unused ctx parameter in ValidateURL (TRIVIAL)

Files modified:

  • commons/net/http/idempotency/idempotency.go
  • commons/security/ssrf/ssrf.go
  • commons/security/ssrf/ssrf_test.go
  • commons/security/ssrf/validate.go

Commit: ea99621

The latest autofix changes are on the fix/coderabbit-autofix-pr419 branch.

@fredcamaral fredcamaral merged commit e0c128b into develop Mar 30, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant