Skip to content

Fix/ssrf alerts target#1665

Open
ygndotgg wants to merge 4 commits into
parseablehq:mainfrom
ygndotgg:fix/ssrf-alerts-target
Open

Fix/ssrf alerts target#1665
ygndotgg wants to merge 4 commits into
parseablehq:mainfrom
ygndotgg:fix/ssrf-alerts-target

Conversation

@ygndotgg
Copy link
Copy Markdown
Contributor

@ygndotgg ygndotgg commented Jun 2, 2026

Fixes #XXXX.

Description

This PR hardens alert target delivery against server-side request forgery by adding an outbound policy for alert-target HTTP
requests.

Previously, authenticated users with alert target permissions could configure webhook, Slack, or AlertManager targets that
caused the Parseable server to send outbound HTTP requests to arbitrary URLs from the server’s network position. That meant
private/internal destinations, loopback services, link-local metadata endpoints, and other network-restricted HTTP services
could be reached if routable from the Parseable host.

The chosen fix is to centralize alert-target outbound networking behind a policy gate. Instead of validating only the submitted
URL string, Parseable now resolves the destination, validates the resolved IPs, applies admin-owned allow/deny policy, disables
redirects/proxies, pins the resolved DNS result into the HTTP client, and revalidates again immediately before alert delivery.

Key changes made in this patch:

  • Added alert target outbound policy configuration with:

    • allowPrivate
    • allowedDomains
    • allowedCidrs
    • deniedDomains
    • deniedCidrs
    • allowInvalidTls
  • Added super-admin APIs for reading, updating, and validating the alert target policy.

  • Applied outbound policy validation during target creation and update.

  • Revalidated outbound policy during alert delivery because stored targets and DNS answers can change after creation.

  • Blocked private, loopback, link-local, multicast, reserved, and other non-public destinations by default.

  • Allowed private/internal destinations only when explicitly enabled by admin policy and matched by an allowlist.

  • Made denied CIDRs/domains take precedence over allowlists.

  • Disabled redirects and proxy use for alert target delivery.

  • Pinned resolved DNS addresses into reqwest before sending.

  • Added focused unit tests for the important policy decision paths.


This PR has:

  • added comments explaining the "why" and the intent of the code wherever would not be obvious for an unfamiliar reader.
  • been tested to ensure log ingestion and log query works.
  • added documentation for new or modified features or behaviors.

Summary by CodeRabbit

  • New Features

    • Added an outbound HTTP policy system for alert targets to control allowed/denied destinations, CIDRs, domains, TLS rules, and private-IP access
    • New admin HTTP endpoints to view, validate, and replace alert target policies
    • Alert targets are validated before saving and re-validated at delivery; authorization headers may be blocked per policy
  • Bug Fixes

    • Policy violations now return structured JSON error responses (400) with an admin hint when outbound policy blocks a request

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 2, 2026

Review Change Stack

Walkthrough

This PR introduces an outbound egress policy system for alert targets. The system validates Slack webhooks, generic webhooks, and AlertManager instances against configurable allow/deny domain and CIDR rules, enforcing TLS policies, blocking sensitive headers, and pinning DNS-resolved addresses before connection. Includes admin HTTP endpoints for viewing and updating the policy.

Changes

Outbound Alert Target Egress Policy

Layer / File(s) Summary
Policy Configuration & Storage
src/alerts/outbound_http_policy.rs, Cargo.toml
AlertTargetPolicyConfig defines allow/deny domains and CIDRs plus TLS/private-IP flags; global ALERT_TARGET_POLICY snapshot with active_policy() and replace_policy() and CIDR validation.
Validation Engine & Request Preparation
src/alerts/outbound_http_policy.rs
PreparedAlertTarget and OutboundPolicyError; prepare_alert_target implements scheme/TLS checks, DNS resolution and pinning, fail-closed IP/CIDR checks, authorization gating, header sanitization, and builds a pinned reqwest::Client. Unit tests exercise policy defaults, CIDR parsing, private-IP behavior, header/auth blocking, Slack HTTPS/host pinning, and TLS-skip enforcement.
Error Handling Integration
src/alerts/mod.rs
Exports outbound_http_policy module; AlertError gains OutboundPolicy(#[from] ...) variant; Actix ResponseError maps this variant to 400 BAD_REQUEST and returns a JSON payload with a fixed blocked message, error text, and an admin hint.
Target Delivery Integration
src/alerts/target.rs
Adds TargetType::validate_outbound_policy for preflight checks; SlackWebHook, OtherWebHook, and AlertManager now call prepare_alert_target per delivery, log and return on policy rejection, use prepared client/headers, and only apply credentials when authorization_allowed.
HTTP Policy Management API
src/handlers/http/alert_target_policy.rs, src/handlers/http/mod.rs, src/handlers/http/modal/server.rs, src/handlers/http/modal/query_server.rs
Adds GET (current policy), PUT (validate then replace), and POST /validate (dry-run) handlers for /admin/alert-target-policy, registers the scope under base_path(), and requires Action::SuperAdmin.
Target CRUD Policy Validation
src/handlers/http/targets.rs
post and update handlers now call validate_outbound_policy().await? before persisting targets.
Dependency Updates
Cargo.toml
Enables tokio's net feature to support DNS resolution and socket operations used by the validation engine.

Sequence Diagram(s)

sequenceDiagram
  participant Admin as Admin API
  participant Handler as alert_target_policy handlers
  participant Validator as validate_policy
  participant Policy as ALERT_TARGET_POLICY

  Admin->>Handler: PUT /admin/alert-target-policy
  Handler->>Validator: validate_policy(&config)
  Validator-->>Handler: Result<(), OutboundPolicyError>
  alt validation passes
    Handler->>Policy: replace_policy(config)
    Policy-->>Handler: Ok(())
    Handler-->>Admin: 200 JSON(config)
  else validation fails
    Validator-->>Handler: OutboundPolicyError
    Handler-->>Admin: 400 JSON(error)
  end
Loading
sequenceDiagram
  participant AlertSender as Alert delivery
  participant Target as TargetType::call
  participant Prepare as prepare_alert_target
  participant DNS as DNS Resolver
  participant Validator as Address/Domain Validator
  participant Client as reqwest::Client

  AlertSender->>Target: send(alert)
  Target->>Prepare: prepare_alert_target(endpoint, kind, headers)
  Prepare->>Prepare: validate scheme & TLS rules
  Prepare->>DNS: resolve host -> addrs
  DNS-->>Prepare: resolved addresses
  Prepare->>Validator: check denied CIDRs & private IPs
  alt policy allows
    Prepare-->>Target: PreparedAlertTarget
    Target->>Client: POST with prepared client & headers/authorization
    Client-->>Target: response
  else policy rejects
    Prepare-->>Target: OutboundPolicyError
    Target->>Target: log rejection & return
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • parseablehq/parseable#1390: Both PRs modify src/alerts/mod.rs's AlertError enum and could conflict at the error-type definition level.

Suggested reviewers

  • nikhilsinhaparseable

Poem

🐰 A rabbit hops through policy gates,
DNS and CIDRs check the fates,
Headers trimmed and TLS held tight,
Admins guard who claims the right,
Alerts go only where they're bright.

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (1 warning, 2 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Fix/ssrf alerts target' is vague and uses slash notation; it does not clearly convey that this adds outbound HTTP policy controls for alert targets to prevent SSRF attacks. Revise the title to be more descriptive, such as 'Add outbound HTTP policy validation for alert targets' or 'Implement SSRF protection for alert target delivery'.
Description check ❓ Inconclusive The PR description covers the goal, rationale, and key changes comprehensively, but the testing checkbox is unchecked despite being required. Confirm whether log ingestion and query testing has been completed, or explicitly document why it was deferred. Mark the testing checkbox if validation was performed.
✅ Passed checks (2 passed)
Check name Status Explanation
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

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.

Copy link
Copy Markdown
Contributor

@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

🧹 Nitpick comments (2)
Cargo.toml (1)

84-91: 💤 Low value

ipnet addition not reflected in summary; otherwise dependency changes look correct.

The net feature on tokio ("^1.43") is valid and required for DNS resolution/socket operations in the policy engine. However, line 91 also adds ipnet = "2" (needed for CIDR parsing in the allow/deny policy), which contradicts the summary's claim that no other dependencies were altered.

Note that ipnet is placed under the "Async and Runtime" section though it's a networking/CIDR utility — consider moving it nearer url/networking deps for clarity, but this is purely cosmetic.

🤖 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 `@Cargo.toml` around lines 84 - 91, Update the PR summary to explicitly mention
the new ipnet = "2" dependency (used for CIDR parsing in allow/deny policy) in
addition to the tokio feature change; in the Cargo.toml diff, ensure the ipnet
addition is clearly associated with networking/url deps rather than the "Async
and Runtime" block by moving the ipnet = "2" line next to other networking
dependencies (the tokio features block and url dependency) so reviewers can
easily see it's a networking/CIDR utility; keep the existing tokio feature
changes (sync, macros, fs, rt-multi-thread, net) unchanged.
src/alerts/outbound_http_policy.rs (1)

167-169: 💤 Low value

Consider propagating client build errors instead of panicking.

While the ClientBuilder configuration is deterministic and unlikely to fail in practice, using .expect() in production code paths is less defensive than propagating the error. If future changes introduce fallible configuration (e.g., loading certificates), this would need updating.

🛡️ Suggested fix

Add a new error variant and propagate:

+    #[error("failed to build HTTP client: {0}")]
+    ClientBuildFailed(String),
     let client = builder
         .build()
-        .expect("alert target HTTP client can be constructed");
+        .map_err(|e| OutboundPolicyError::ClientBuildFailed(e.to_string()))?;
🤖 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 `@src/alerts/outbound_http_policy.rs` around lines 167 - 169, Replace the
panic-causing .expect("alert target HTTP client can be constructed") on
builder.build() with proper error propagation: add a new error variant (e.g.,
OutboundHttpClientBuildError or ClientBuildError) to the module's error enum,
change the containing function's return type to Result<..., ErrorType>, and
replace builder.build().expect(...) with builder.build().map_err(|e|
ErrorType::OutboundHttpClientBuildError(e.into()))? (or equivalent) so the
client construction failure is returned to callers; update any call sites and
tests to handle the propagated Result accordingly.
🤖 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 `@src/alerts/outbound_http_policy.rs`:
- Around line 306-323: In denied_ipv6, add explicit checks to reject IPv6
tunneling prefixes 6to4 (2002::/16) and Teredo (2001::/32) before falling back
to mapped-IPv4 handling: inspect ip.segments()[0] and return true when it equals
0x2002 (6to4) or equals 0x2001 (Teredo /32), then continue with the existing
mapped-IPv4 call to denied_ipv4 and other checks; this ensures embedded IPv4
addresses in those tunneling ranges cannot bypass the private-IP blocking.

---

Nitpick comments:
In `@Cargo.toml`:
- Around line 84-91: Update the PR summary to explicitly mention the new ipnet =
"2" dependency (used for CIDR parsing in allow/deny policy) in addition to the
tokio feature change; in the Cargo.toml diff, ensure the ipnet addition is
clearly associated with networking/url deps rather than the "Async and Runtime"
block by moving the ipnet = "2" line next to other networking dependencies (the
tokio features block and url dependency) so reviewers can easily see it's a
networking/CIDR utility; keep the existing tokio feature changes (sync, macros,
fs, rt-multi-thread, net) unchanged.

In `@src/alerts/outbound_http_policy.rs`:
- Around line 167-169: Replace the panic-causing .expect("alert target HTTP
client can be constructed") on builder.build() with proper error propagation:
add a new error variant (e.g., OutboundHttpClientBuildError or ClientBuildError)
to the module's error enum, change the containing function's return type to
Result<..., ErrorType>, and replace builder.build().expect(...) with
builder.build().map_err(|e| ErrorType::OutboundHttpClientBuildError(e.into()))?
(or equivalent) so the client construction failure is returned to callers;
update any call sites and tests to handle the propagated Result accordingly.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 307e02c4-1433-45cc-90f4-a9ee10f8c67c

📥 Commits

Reviewing files that changed from the base of the PR and between cefe210 and fa45266.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (9)
  • Cargo.toml
  • src/alerts/mod.rs
  • src/alerts/outbound_http_policy.rs
  • src/alerts/target.rs
  • src/handlers/http/alert_target_policy.rs
  • src/handlers/http/mod.rs
  • src/handlers/http/modal/query_server.rs
  • src/handlers/http/modal/server.rs
  • src/handlers/http/targets.rs

Comment thread src/alerts/outbound_http_policy.rs
@ygndotgg ygndotgg force-pushed the fix/ssrf-alerts-target branch from fa45266 to b8f9628 Compare June 2, 2026 13:08
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