Constrain webhook subscription URLs to prevent SSRF from user-supplied endpoints
@Ranjeet2063 is already working on this.
Since Oct 9, 2026.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 22/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- nodejs, typescript
Research direction
Start by reading src/infrastructure/webhooks/webhook-subscription.service.ts to see whether any URL validation exists, then trace the outbound request in webhook-delivery.service.ts. Registration and delivery both need IP-range checks on the resolved address, and redirects must be disabled or re-validated. Done means the listed SSRF cases have tests, but the issue says the module has no tests yet and the ownership fix is tracked separately.
Written by the indexing model from the issue text.
Description
Problem
src/infrastructure/webhooks/ lets a subscriber register an endpoint that the API will later POST to. The module has five services, none tested:
webhook-subscription.service.ts— presumably stores the target URLwebhook-delivery.service.ts— performs the outbound requestwebhook-processor.service.ts,webhook-event.service.ts,webhook-hmac.service.ts
And the controller that accepts registrations (webhook.controller.ts) hardcodes const userId = "system" at eight sites — lines 53, 65, 76, 89, 100, 110, 127, and 196 — meaning subscription ownership is not enforced either.
If the stored URL is not validated, an authenticated user can register an endpoint pointing at internal infrastructure and the API will dutifully request it, with retries and error logging that confirm reachability.
Why it matters
This is textbook SSRF. Targets include cloud metadata endpoints (169.254.169.254), internal admin services, and the database host. The retry logic makes it worse — repeated attempts are how SSRF scanners distinguish a filtered host from a closed port.
axios is a dependency and @nestjs/axios is present, so the outbound mechanism exists; the question is what validation happens before it.
Suggested Direction
- Verify the current state first. Read
webhook-subscription.service.tsand confirm whether any URL validation occurs. If a scheme/host allowlist already exists, this issue shrinks to tightening it. - Validate at registration time, not at delivery time — reject obviously dangerous destinations up front:
- Scheme allowlist:
httpsonly (plushttpexplicitly for development), rejectingfile:,gopher:,ftp:,data:. - Resolve the hostname and reject private, loopback, link-local, and unique-local ranges —
127.0.0.0/8,10/8,172.16/12,192.168/16,169.254/16,::1,fc00::/7. - Reject non-standard ports unless explicitly configured.
- Scheme allowlist:
- Re-resolve and re-check at delivery time. DNS rebinding defeats validation done only at registration: a hostname that resolved to a public address can resolve to
127.0.0.1when the webhook fires. This is the part most implementations miss. - Disable redirect following on the outbound client, or re-validate each hop — a 302 to an internal address bypasses the check entirely.
- Fix the ownership problem too: with
userId = "system", any user can read or overwrite any other user's subscriptions, which is both a data leak and a way to redirect someone else's webhooks. That is tracked in a separate issue; note the dependency here. - Add response-code and timing hygiene — avoid logging full response bodies from user-supplied endpoints, and apply the rate limiter to outbound delivery.
Constraints
- Naive string checks on the URL (
includes("127.0.0.1")) are trivially bypassed (http://127.1/,http://0x7f000001/, decimal IPs, DNS rebinding). Use proper IP-range checking on the resolved address. - Some users legitimately need internal webhooks in development. Make the strict policy the default with an explicit, documented opt-out for non-production.
- This module has zero tests today — the webhook test issue should land first or alongside.
Acceptance Criteria
- Only
https(andhttpin development) schemes are accepted. - Hostnames resolving to private, loopback, or link-local addresses are rejected at registration and at delivery.
- Redirects are not followed blindly, or each hop is re-validated.
- Tests cover metadata-IP, loopback, private-range, redirect-to-internal, and DNS-rebinding cases.
- Ownership is enforced so a user cannot register a webhook against another user's account.
- Outbound delivery is rate-limited and does not log full response bodies.
- Dominant language
- TypeScript
- Stars
- 7
- Forks
- 55
- Avg merge
- 1d 17h
- Merged PRs (30d)
- 5
Getting set up
- Ships a Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from SourceXXL/alian_structure-api
-
good first issue help wanted security
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
SourceXXL/alian_structure-api#266 ·
-
Parallelize the cache-warming loop in `cache-warming.service.ts` — warmers run strictly sequentiallyPossibly taken @syntaxsuuvam-spec claimed this 2 days ago. Opengood first issue performance
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
SourceXXL/alian_structure-api#240 ·
-
documentation help wanted intermediate refactor
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
SourceXXL/alian_structure-api#215 ·
-
good first issue refactor tooling
Difficulty 2/5 1-3 hours Newbie friendliness 80/100
SourceXXL/alian_structure-api#199 ·
-
help wanted intermediate refactor
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
SourceXXL/alian_structure-api#196 ·
All issues in SourceXXL/alian_structure-api
Similar issues
-
Difficulty 1/5 1-3 hours Newbie friendliness 70/100
siyuan-note/siyuan#20517 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
NaturalIntelligence/fast-xml-parser#890 · 1 comment ·
Maintainers usually reply within 3 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
zsviczian/obsidian-excalidraw-plugin#3000 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
pnpm/get.pnpm.io#68 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
nuxt-modules/better-auth#578 · 2 comments ·
Maintainers usually reply within 1 day