You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
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:43validates the URL with onlystarts_with("http://") || starts_with("https://"). No private/loopback/link-local check.get_repoonly 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:85doesclient.post(&hook.url)on events (e.g. fired fromapi/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>/...orhttp://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
require_ownerhelper (api/visibility.rs), which matches the caller against both the full DID and its short form. A bareauth.0 == record.owner_didequality would 403 a legitimate owner whose stored DID is the full form.