Skip to content

fix: validate derived backend endpoints#152

Merged
jingxiang-z merged 4 commits intomainfrom
fix/validate-endpoints
Apr 1, 2026
Merged

fix: validate derived backend endpoints#152
jingxiang-z merged 4 commits intomainfrom
fix/validate-endpoints

Conversation

@jingxiang-z
Copy link
Copy Markdown
Collaborator

@jingxiang-z jingxiang-z commented Mar 31, 2026

Summary

  • add shared endpoint validation helpers for backend and local daemon URLs
  • validate enrollment input before deriving enroll, metrics, logs, and nonce endpoints
  • validate stored nonce, metrics, and logs endpoints before use and add regression tests for hostile URL inputs

Why

The agent was trusting derived or stored endpoint values too loosely in a few paths. The main trust anchor is the enrollment endpoint shape, and downstream endpoints should get at least basic validation before use.

Impact

This hardens enroll, status, inject, attestation nonce fetches, and exporter metadata refreshes against malformed or unsafe endpoint values while preserving the intended localhost client flow.

Validation

  • go test ./internal/exporter ./cmd/fleetint ./internal/endpoint ./internal/attestation

Summary by CodeRabbit

  • Bug Fixes

    • Strengthened URL validation for server and backend endpoints and clarified error text for invalid endpoints
    • Prevented use of non-loopback or malformed server URLs and disallowed non-HTTPS backend endpoints
    • Safer endpoint construction for health/nonce/enroll/metrics/logs and injection flows; injection requests now use a client with a 5s timeout
  • Tests

    • Added security tests rejecting non-local server URLs and expanded endpoint validation coverage

Signed-off-by: Jingxiang Zhang <jingzhang@nvidia.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 31, 2026

📝 Walkthrough

Walkthrough

Introduces an internal endpoint package for URL parsing/validation and joining; integrates it across CLI commands, attestation, and exporter modules to validate server and backend endpoints (loopback/local checks and HTTPS enforcement) and replace ad-hoc URL building with centralized helpers.

Changes

Cohort / File(s) Summary
Endpoint package & tests
internal/endpoint/endpoint.go, internal/endpoint/endpoint_test.go
Added centralized URL helpers: ValidateLocalServerURL, ValidateBackendEndpoint, and JoinPath plus parsing/validation helpers and unit tests covering accepted/rejected inputs.
CLI command updates
cmd/fleetint/enroll.go, cmd/fleetint/inject.go, cmd/fleetint/status.go
Replaced local URL parsing/concatenation with endpoint.Validate* and endpoint.JoinPath; improved error messages; inject now uses an http.Client with timeout; enroll no longer constructs health endpoint locally.
CLI security tests
cmd/fleetint/security_test.go
Added tests asserting status and inject commands reject non-local, credential-containing, or path-containing --server-url values with appropriate error messages.
Attestation changes & tests
internal/attestation/attestation.go, internal/attestation/attestation_test.go
Added getValidatedNonceEndpoint using ValidateBackendEndpoint; updated getNonce to use validated URL. Added tests using temporary SQLite metadata to verify nonce endpoint retrieval and HTTPS enforcement.
Exporter changes & tests
internal/exporter/exporter.go, internal/exporter/exporter_test.go
Metrics/logs endpoints from metadata are now validated via ValidateBackendEndpoint; invalid endpoints are logged and cleared. Tests updated to expect HTTPS and to assert invalid endpoints are ignored.
Minor import adjustments
cmd/fleetint/enroll.go, cmd/fleetint/inject.go, cmd/fleetint/status.go, internal/exporter/exporter.go
Dropped/added imports to use internal/endpoint and adjust net/url and time where required.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped in to tidy each URL line,
HTTPS guarded, loopback kept fine,
Paths now joined with care and grace,
No sneaky creds can take their place,
A rabbit’s nod — your endpoints shine ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: validate derived backend endpoints' directly and accurately describes the main objective of the PR, which centers on adding endpoint validation helpers and validating backend endpoints throughout the codebase.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/validate-endpoints

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

Signed-off-by: Jingxiang Zhang <jingzhang@nvidia.com>
Signed-off-by: Jingxiang Zhang <jingzhang@nvidia.com>
@jingxiang-z jingxiang-z marked this pull request as ready for review March 31, 2026 23:26
@jingxiang-z jingxiang-z self-assigned this Mar 31, 2026
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

Caution

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

⚠️ Outside diff range comments (1)
cmd/fleetint/inject.go (1)

114-117: ⚠️ Potential issue | 🟠 Major

Add explicit HTTP timeouts for inject/clear requests.

http.Post uses the default client without a timeout, so these calls can hang indefinitely under network/server stalls. The codebase already establishes a pattern of using &http.Client{Timeout: 5 * time.Second} in status.go—apply the same approach here.

Suggested patch
 import (
 	"bytes"
 	"encoding/json"
 	"fmt"
 	"net/http"
 	"net/url"
+	"time"
@@
-	resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonData))
+	client := &http.Client{Timeout: 5 * time.Second}
+	resp, err := client.Post(url, "application/json", bytes.NewBuffer(jsonData))
@@
-	resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonData))
+	client := &http.Client{Timeout: 5 * time.Second}
+	resp, err := client.Post(url, "application/json", bytes.NewBuffer(jsonData))

Also applies to: 187-190

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

In `@cmd/fleetint/inject.go` around lines 114 - 117, Replace the use of the
package-level http.Post (which has no timeout) with a local http.Client
configured with a timeout (e.g. client := &http.Client{Timeout: 5 *
time.Second}) and call client.Post for both places where http.Post is used in
this file; ensure you keep the existing error wrapping (fmt.Errorf("failed to
make request to %s: %w", url, err)), handle resp as before, and remember to
close resp.Body after a successful response. Refer to the http.Post calls in
this file (the inject/clear request handlers) and mirror the timeout pattern
used in status.go.
🧹 Nitpick comments (2)
internal/exporter/exporter.go (1)

268-309: Consider extracting shared endpoint-refresh logic for metrics/logs.

Both branches duplicate read/validate/apply/log flow. A small helper would reduce drift risk and keep future policy changes consistent.

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

In `@internal/exporter/exporter.go` around lines 268 - 309, Extract the duplicated
read/validate/apply/log flow into a small helper (e.g., refreshEndpoint(ctx
context.Context, metaKey string, current string) string or a method on the
exporter like (e *Exporter) refreshEndpoint(ctx context.Context, metaKey string,
getCurrent func() string, setCurrent func(string))) that uses
pkgmetadata.ReadMetadata and endpoint.ValidateBackendEndpoint, preserves the
existing behavior for invalid values (log.Logger.Errorw + treat as empty),
error-on-read (log.Logger.Errorw), and the update/clear logging
(log.Logger.Infow) and returns or applies the final endpoint string; then
replace the two blocks that handle "metrics_endpoint" and "logs_endpoint" to
call this helper and set e.options.config.MetricsEndpoint /
e.options.config.LogsEndpoint accordingly so semantics remain identical but
duplication is removed.
cmd/fleetint/enroll.go (1)

62-86: Optional: reduce repeated endpoint join blocks.

This section is correct; you could simplify maintenance by extracting a tiny local helper for repeated "api/v1/health/*" joins.

♻️ Proposed refactor
-	// Construct enroll endpoint
-	enrollEndpoint, err := endpoint.JoinPath(baseURL, "api", "v1", "health", "enroll")
+	buildHealthEndpoint := func(name string) (string, error) {
+		return endpoint.JoinPath(baseURL, "api", "v1", "health", name)
+	}
+
+	// Construct enroll endpoint
+	enrollEndpoint, err := buildHealthEndpoint("enroll")
 	if err != nil {
 		return fmt.Errorf("failed to construct enroll endpoint: %w", err)
 	}
@@
-	metricsEndpoint, err := endpoint.JoinPath(baseURL, "api", "v1", "health", "metrics")
+	metricsEndpoint, err := buildHealthEndpoint("metrics")
 	if err != nil {
 		return fmt.Errorf("failed to construct metrics endpoint: %w", err)
 	}
-	logsEndpoint, err := endpoint.JoinPath(baseURL, "api", "v1", "health", "logs")
+	logsEndpoint, err := buildHealthEndpoint("logs")
 	if err != nil {
 		return fmt.Errorf("failed to construct logs endpoint: %w", err)
 	}
-	nonceEndpoint, err := endpoint.JoinPath(baseURL, "api", "v1", "health", "nonce")
+	nonceEndpoint, err := buildHealthEndpoint("nonce")
 	if err != nil {
 		return fmt.Errorf("failed to construct nonce endpoint: %w", err)
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/fleetint/enroll.go` around lines 62 - 86, Extract a small helper function
(e.g., buildHealthEndpoint) to DRY the repeated endpoint.JoinPath(baseURL,
"api", "v1", "health", ... ) logic used to create enrollEndpoint,
metricsEndpoint, logsEndpoint, and nonceEndpoint; replace each direct
endpoint.JoinPath call with buildHealthEndpoint(ctxSegment) that returns
(string, error) and reuse it for "enroll", "metrics", "logs", and "nonce" so
error handling remains identical but the join code is centralized.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/endpoint/endpoint.go`:
- Around line 35-43: ValidateLocalServerURL currently permits server URLs with
non-root paths; update it to reject any URL whose parsed.Path is non-empty or
not equal to "/" so local daemon base URLs must be root-only. In the function
(around the existing host/isLoopbackHost checks), add a check using parsed.Path
(or parsed.EscapedPath) and return an error like "server URL must not contain a
path; use the root URL" when the path is not empty or "/" to prevent JoinPath
from producing unintended routes. Ensure the error message references the
offending path for clarity and keep the isLoopbackHost and host validations
intact.

---

Outside diff comments:
In `@cmd/fleetint/inject.go`:
- Around line 114-117: Replace the use of the package-level http.Post (which has
no timeout) with a local http.Client configured with a timeout (e.g. client :=
&http.Client{Timeout: 5 * time.Second}) and call client.Post for both places
where http.Post is used in this file; ensure you keep the existing error
wrapping (fmt.Errorf("failed to make request to %s: %w", url, err)), handle resp
as before, and remember to close resp.Body after a successful response. Refer to
the http.Post calls in this file (the inject/clear request handlers) and mirror
the timeout pattern used in status.go.

---

Nitpick comments:
In `@cmd/fleetint/enroll.go`:
- Around line 62-86: Extract a small helper function (e.g., buildHealthEndpoint)
to DRY the repeated endpoint.JoinPath(baseURL, "api", "v1", "health", ... )
logic used to create enrollEndpoint, metricsEndpoint, logsEndpoint, and
nonceEndpoint; replace each direct endpoint.JoinPath call with
buildHealthEndpoint(ctxSegment) that returns (string, error) and reuse it for
"enroll", "metrics", "logs", and "nonce" so error handling remains identical but
the join code is centralized.

In `@internal/exporter/exporter.go`:
- Around line 268-309: Extract the duplicated read/validate/apply/log flow into
a small helper (e.g., refreshEndpoint(ctx context.Context, metaKey string,
current string) string or a method on the exporter like (e *Exporter)
refreshEndpoint(ctx context.Context, metaKey string, getCurrent func() string,
setCurrent func(string))) that uses pkgmetadata.ReadMetadata and
endpoint.ValidateBackendEndpoint, preserves the existing behavior for invalid
values (log.Logger.Errorw + treat as empty), error-on-read (log.Logger.Errorw),
and the update/clear logging (log.Logger.Infow) and returns or applies the final
endpoint string; then replace the two blocks that handle "metrics_endpoint" and
"logs_endpoint" to call this helper and set e.options.config.MetricsEndpoint /
e.options.config.LogsEndpoint accordingly so semantics remain identical but
duplication is removed.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: be341500-ee91-4d0c-b98c-b830cbd44e5a

📥 Commits

Reviewing files that changed from the base of the PR and between cd41477 and b816e08.

📒 Files selected for processing (10)
  • cmd/fleetint/enroll.go
  • cmd/fleetint/inject.go
  • cmd/fleetint/security_test.go
  • cmd/fleetint/status.go
  • internal/attestation/attestation.go
  • internal/attestation/attestation_test.go
  • internal/endpoint/endpoint.go
  • internal/endpoint/endpoint_test.go
  • internal/exporter/exporter.go
  • internal/exporter/exporter_test.go

Comment thread internal/endpoint/endpoint.go
Signed-off-by: Jingxiang Zhang <jingzhang@nvidia.com>
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.

🧹 Nitpick comments (2)
internal/endpoint/endpoint.go (1)

62-63: Add a nil guard in JoinPath to avoid panic on invalid callers.

base.String() on Line 63 will panic if base == nil. Returning an error keeps this helper fail-safe.

Proposed patch
 func JoinPath(base *url.URL, elems ...string) (string, error) {
+	if base == nil {
+		return "", fmt.Errorf("base URL is nil")
+	}
 	return url.JoinPath(base.String(), elems...)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/endpoint/endpoint.go` around lines 62 - 63, The JoinPath helper
currently calls base.String() which will panic if base is nil; add a nil check
at the start of JoinPath (func JoinPath(base *url.URL, elems ...string)) and
return a non-nil error when base == nil instead of calling base.String();
otherwise proceed to call url.JoinPath with base.String() and elems. Ensure the
returned error is descriptive (e.g., "nil base URL") so callers can handle it.
cmd/fleetint/inject.go (1)

115-117: Consider extracting shared POST logic for inject/clear paths.

Both branches create the same timeout client and POST flow; a small helper would reduce duplication and drift risk.

Refactor sketch
+func postJSON(url string, body []byte) (*http.Response, error) {
+	client := &http.Client{Timeout: 5 * time.Second}
+	return client.Post(url, "application/json", bytes.NewBuffer(body))
+}
...
-	client := &http.Client{Timeout: 5 * time.Second}
-	resp, err := client.Post(url, "application/json", bytes.NewBuffer(jsonData))
+	resp, err := postJSON(url, jsonData)
...
-	client := &http.Client{Timeout: 5 * time.Second}
-	resp, err := client.Post(url, "application/json", bytes.NewBuffer(jsonData))
+	resp, err := postJSON(url, jsonData)

Also applies to: 189-191

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

In `@cmd/fleetint/inject.go` around lines 115 - 117, The inject.go code duplicates
HTTP POST setup (creating a timeout http.Client and calling client.Post with
"application/json") in the inject and clear branches; extract that shared logic
into a small helper like postJSONWithTimeout(url string, jsonData []byte,
timeout time.Duration) (resp *http.Response, err error) (or similarly named) and
replace the duplicated blocks in the inject/clear handlers to call that helper;
update callers where the helper is used (the blocks around client :=
&http.Client{Timeout: 5 * time.Second} and client.Post(...)) and ensure errors
and resp are handled the same as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@cmd/fleetint/inject.go`:
- Around line 115-117: The inject.go code duplicates HTTP POST setup (creating a
timeout http.Client and calling client.Post with "application/json") in the
inject and clear branches; extract that shared logic into a small helper like
postJSONWithTimeout(url string, jsonData []byte, timeout time.Duration) (resp
*http.Response, err error) (or similarly named) and replace the duplicated
blocks in the inject/clear handlers to call that helper; update callers where
the helper is used (the blocks around client := &http.Client{Timeout: 5 *
time.Second} and client.Post(...)) and ensure errors and resp are handled the
same as before.

In `@internal/endpoint/endpoint.go`:
- Around line 62-63: The JoinPath helper currently calls base.String() which
will panic if base is nil; add a nil check at the start of JoinPath (func
JoinPath(base *url.URL, elems ...string)) and return a non-nil error when base
== nil instead of calling base.String(); otherwise proceed to call url.JoinPath
with base.String() and elems. Ensure the returned error is descriptive (e.g.,
"nil base URL") so callers can handle it.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 47368f96-7175-4a0c-bc2c-920823f83827

📥 Commits

Reviewing files that changed from the base of the PR and between b816e08 and b27beda.

📒 Files selected for processing (3)
  • cmd/fleetint/inject.go
  • cmd/fleetint/security_test.go
  • internal/endpoint/endpoint.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmd/fleetint/security_test.go

Copy link
Copy Markdown
Collaborator

@ambermingxin ambermingxin left a comment

Choose a reason for hiding this comment

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

Looks good to me.

@jingxiang-z jingxiang-z merged commit 752b61f into main Apr 1, 2026
9 checks passed
@jingxiang-z jingxiang-z deleted the fix/validate-endpoints branch April 1, 2026 16:55
jingxiang-z added a commit that referenced this pull request Apr 14, 2026
Signed-off-by: Jingxiang Zhang <jingzhang@nvidia.com>
jingxiang-z added a commit that referenced this pull request Apr 14, 2026
Signed-off-by: Jingxiang Zhang <jingzhang@nvidia.com>
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.

2 participants