Skip to content

Restore and harden credential masking for /targets#1698

Merged
parmesant merged 1 commit into
parseablehq:mainfrom
ygndotgg:fix-alerts-masking
Jun 23, 2026
Merged

Restore and harden credential masking for /targets#1698
parmesant merged 1 commit into
parseablehq:mainfrom
ygndotgg:fix-alerts-masking

Conversation

@ygndotgg

@ygndotgg ygndotgg commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Fixes #1693.

Description

Resolves a credential leakage vulnerability where notification target secrets (webhook tokens, basic-auth passwords, sensitive headers) were exposed in cleartext via the /targets API.

Root Cause: Target masking was disabled in PR #1398 and never restored. The Target::mask() calls were commented out in all handlers.

Why not just uncomment: The legacy masking logic was flawed—it leaked custom HTTP headers in cleartext and used unsafe byte-slicing (&endpoint[..20]) that risked partial secret leakage and panics on non-ASCII boundaries.

Solution:

  • Added safe mask_url() method using the url crate API (no byte-slicing)
  • Headers in webhook targets are now fully redacted
  • Re-enabled target.mask() in all 5 HTTP handlers
  • Added regression test verifying secrets never appear in masked output

This PR has:

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

Summary by CodeRabbit

  • Bug Fixes
    • Improved API response security by returning masked target data for create, read, update, delete, and list operations.
    • Endpoint masking is now scheme-based (e.g., {scheme}://********), avoiding accidental leakage of full URLs.
    • Webhook/header redaction is stronger by replacing all header values with ********.
    • Credential masking now preserves username (when present) while redacting password; omitted credentials are returned as null.
  • Tests
    • Added coverage to ensure masked JSON never contains plaintext passwords and always includes the ******** marker.

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Target::mask is updated to redact webhook/endpoint URLs using scheme-based masking ({scheme}://******** instead of path truncation). OtherWebHook masking now redacts all header values. All HTTP target handlers (post, get, update, delete, list) now return masked representations instead of raw target objects.

Changes

Target Secrets Redaction

Layer / File(s) Summary
Target::mask scheme-based redaction and header masking
src/alerts/target.rs
Updates Slack, OtherWebHook, and AlertManager masking branches to redact endpoints as {scheme}://********. OtherWebHook masking now replaces each header value with ******** instead of returning originals. AlertManager applies the same scheme-based endpoint redaction whether or not auth is present.
Unit test for masked target secret redaction
src/alerts/target.rs
Adds test_masked_target_hides_secrets that constructs an AlertManager target with credentials and asserts the masked JSON excludes the plaintext password and contains ********.
HTTP target handlers apply mask()
src/handlers/http/targets.rs
post, get, update, delete, and list all return target.mask() output instead of the raw target object, enabling redaction on every API response path.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~13 minutes

Possibly related PRs

  • parseablehq/parseable#1365: Modifies the same Target::mask implementation in src/alerts/target.rs and the same handler return paths in src/handlers/http/targets.rs for masking sensitive target fields.

Poem

🐇 Hop hop, no secrets shall leak through today,
The endpoints are masked in the scheme's clear display,
Eight stars guard passwords and headers with care,
No tokens or credentials drift through the air,
From AlertManager to Slack, they're all tucked away,
Your secrets are safe in the /******** way! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% 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
Title check ✅ Passed The title 'Restore and harden credential masking for /targets' accurately describes the main change: restoring disabled masking and implementing improvements to it.
Description check ✅ Passed The description provides sufficient detail including the root cause, why simple uncommenting is inadequate, the solution approach, and key improvements made.
Linked Issues check ✅ Passed The PR fully addresses issue #1693 by re-enabling target.mask() in all handlers, implementing safer masking logic, redacting headers, and adding regression tests.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the credential leakage vulnerability through masking implementation and handler updates.

✏️ 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (1)
src/alerts/target.rs (1)

663-691: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Consider extending coverage to Slack/Other and embedded-userinfo cases.

The regression test only exercises the AlertManager branch with separate auth. The higher-risk paths — Slack URLs (secret lives entirely in the path), Other header values, and endpoints with embedded user:password@ userinfo — are not asserted. Adding a case that constructs an endpoint like https://user:s3cret@host/secretpath?token=abc and asserts the secret is absent would directly guard the leak noted in mask_url.

🤖 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/target.rs` around lines 663 - 691, The
test_masked_target_hides_secrets function only covers the AlertManager variant
with separate auth credentials and does not test higher-risk secret exposure
paths. Extend the test by adding additional assertions or test cases that verify
secrets embedded directly in endpoint URLs (such as user:password@host patterns,
path components, and query parameters) are properly masked across other
TargetType variants like Slack and Other. Construct test endpoints with embedded
secrets (e.g., https://user:s3cret@host/secretpath?token=abc) and assert these
secrets are absent from the masked output, ensuring the mask method properly
handles all the cases that mask_url is designed to protect against.
🤖 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/target.rs`:
- Around line 198-208: The mask_url function has a security vulnerability where
it fails to strip userinfo (username and password) from URLs, allowing
credentials to leak in the masked output. Fix this by reliably removing userinfo
before serialization - test carefully whether setting both username and password
via the Url API methods fully removes the userinfo component, or alternatively
reconstruct the URL to include only the scheme, host, and port components.
Additionally, remove the redundant segments.clear() call since the subsequent
set_path call will overwrite the entire path anyway, and update the doc comment
to accurately describe the function's behavior as redacting userinfo and
removing path, query, and fragment components rather than just obliterating the
trailing path segment.

---

Nitpick comments:
In `@src/alerts/target.rs`:
- Around line 663-691: The test_masked_target_hides_secrets function only covers
the AlertManager variant with separate auth credentials and does not test
higher-risk secret exposure paths. Extend the test by adding additional
assertions or test cases that verify secrets embedded directly in endpoint URLs
(such as user:password@host patterns, path components, and query parameters) are
properly masked across other TargetType variants like Slack and Other. Construct
test endpoints with embedded secrets (e.g.,
https://user:s3cret@host/secretpath?token=abc) and assert these secrets are
absent from the masked output, ensuring the mask method properly handles all the
cases that mask_url is designed to protect against.
🪄 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: 90ac2817-af22-4381-b9c6-dbe25b5613f8

📥 Commits

Reviewing files that changed from the base of the PR and between 595af02 and fd7d7f2.

📒 Files selected for processing (2)
  • src/alerts/target.rs
  • src/handlers/http/targets.rs

Comment thread src/alerts/target.rs Outdated
@ygndotgg ygndotgg force-pushed the fix-alerts-masking branch from fd7d7f2 to cc1dcdd Compare June 23, 2026 07:14
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 23, 2026
- Add mask_url() using url based parsing
- Apply consistent masking across Slack, Webhook, and AlertManager types.
- Add strict unit tests enforcing that secrets never hit the serializer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
src/alerts/target.rs (1)

225-235: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Basic-auth username is still returned in cleartext.

The endpoint and password are now redacted, but auth.username is serialized as-is. The linked issue explicitly lists AlertManager "basic-auth username and password combinations" as exposed secrets, so any GetAlert-scoped reader still recovers half of every basic-auth credential. Confirm whether exposing the username is intentional; if not, redact it (e.g. ********) like the other fields.

🛡️ Proposed redaction
                 if let Some(auth) = alert_manager.auth {
                     let password = "********";
                     json!({
                         "name":self.name,
                         "type":"webhook",
                         "endpoint":endpoint,
-                        "username":auth.username,
+                        "username":"********",
                         "password":password,
                         "skipTlsCheck":alert_manager.skip_tls_check,
                         "id":self.id
                     })
🤖 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/target.rs` around lines 225 - 235, The basic-auth username is
being exposed in cleartext in the JSON response object while the password is
properly redacted with "********". In the alert_manager JSON object creation,
the "username" field is currently serialized with auth.username directly. Change
this to use a redacted value (such as "********") matching the approach used for
the password field, so both the username and password are masked in the
response.
🧹 Nitpick comments (1)
src/alerts/target.rs (1)

648-677: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extend coverage to the new OtherWebHook header redaction.

This test only exercises the AlertManager branch. The most novel part of this change—redacting every OtherWebHook header value while preserving keys—has no regression test. A target with a header like Authorization: Bearer <token> should be asserted to no longer contain the token in masked output. Consider adding a TargetType::Other case (and a Slack case) to lock in the redaction guarantee across all branches.

🤖 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/target.rs` around lines 648 - 677, The
test_masked_target_hides_secrets function currently only covers the AlertManager
branch and does not test the new OtherWebHook header redaction feature. Extend
this test to include additional test cases for TargetType::Other with headers
containing sensitive values (such as "Authorization: Bearer <token>") and a
TargetType::Slack case. For each new case, create a masked Target and verify
that the sensitive header values do not appear in the serialized output while
the redaction marker "********" is present, similar to the existing AlertManager
assertions. This ensures the header redaction guarantee is maintained across all
target type branches.
🤖 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.

Outside diff comments:
In `@src/alerts/target.rs`:
- Around line 225-235: The basic-auth username is being exposed in cleartext in
the JSON response object while the password is properly redacted with
"********". In the alert_manager JSON object creation, the "username" field is
currently serialized with auth.username directly. Change this to use a redacted
value (such as "********") matching the approach used for the password field, so
both the username and password are masked in the response.

---

Nitpick comments:
In `@src/alerts/target.rs`:
- Around line 648-677: The test_masked_target_hides_secrets function currently
only covers the AlertManager branch and does not test the new OtherWebHook
header redaction feature. Extend this test to include additional test cases for
TargetType::Other with headers containing sensitive values (such as
"Authorization: Bearer <token>") and a TargetType::Slack case. For each new
case, create a masked Target and verify that the sensitive header values do not
appear in the serialized output while the redaction marker "********" is
present, similar to the existing AlertManager assertions. This ensures the
header redaction guarantee is maintained across all target type branches.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 0c4cef73-8d25-448d-b1c6-a9ea59f84b38

📥 Commits

Reviewing files that changed from the base of the PR and between cc1dcdd and 38b707f.

📒 Files selected for processing (2)
  • src/alerts/target.rs
  • src/handlers/http/targets.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/handlers/http/targets.rs

@parmesant parmesant merged commit f307c49 into parseablehq:main Jun 23, 2026
12 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.

Notification target secrets (webhook tokens, basic-auth passwords) exposed in cleartext to any low-privilege user via the /targets API

2 participants