Skip to content

Webhook delivery is an unvalidated SSRF sink with no repo-ownership check #81

Description

@beardthelion

Webhook creation accepts any URL behind only a scheme check, and the create handler does not verify the caller owns the repo. On repo events the node POSTs to the stored URL, so this is an SSRF sink reachable by any authenticated DID.

Where

  • crates/gitlawb-node/src/api/webhooks.rs:43 validates the URL with only starts_with("http://") || starts_with("https://"). No private/loopback/link-local check.
  • The handler calls get_repo only to confirm the repo exists; it never checks ownership. The route's auth layers verify identity but not per-repo ownership.
  • crates/gitlawb-node/src/webhooks.rs:85 does client.post(&hook.url) on events (e.g. fired from api/pulls.rs:84), with no re-validation.

Impact

Any DID that can authenticate can register a webhook on any repo with url=http://127.0.0.1:<port>/... or http://169.254.169.254/latest/meta-data/... and make the node POST to internal/loopback/metadata endpoints. The shared client also follows redirects (see the related outbound-redirect finding on #78), so even a public URL can be bounced inward.

Suggested fix

  • Gate the URL through the same public-host validator used for peers, but note that validator still needs the IPv4-mapped/CGNAT/trailing-dot tightening tracked on fix(security): reject non-public peer URLs + prune poisoned peers #78, otherwise the webhook path inherits those same bypasses.
  • Add an ownership check using the existing require_owner helper (api/visibility.rs), which matches the caller against both the full DID and its short form. A bare auth.0 == record.owner_did equality would 403 a legitimate owner whose stored DID is the full form.
  • Harden the delivery client (redirect policy + connect-time guard), same as the peer clients.

Metadata

Metadata

Assignees

No one assigned

    Labels

    crate:nodegitlawb-node — the serving node and REST APIkind:securityVulnerability fix or hardeningsev:highMajor break or real security/trust risk, no easy workaroundsubsystem:apiNode REST API request/response surface

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions