[spark-compete] fix: validate previewUrl against private/reserved hosts before fetch - #843
Open
yossweh wants to merge 3 commits into
Open
[spark-compete] fix: validate previewUrl against private/reserved hosts before fetch#843yossweh wants to merge 3 commits into
yossweh wants to merge 3 commits into
Conversation
Add isPrivateOrReservedHost() check to httpPreviewIsReachable() to prevent SSRF via relay event previewUrl fields. Blocks fetches to: - Loopback addresses (127.0.0.1, localhost, ::1) - Private ranges (10.x, 172.16-31.x, 192.168.x) - Link-local (169.254.x) - Cloud metadata endpoints (metadata.google.internal) - Internal/local domains (.internal, .local)
NoRegretz
pushed a commit
to NoRegretz/spark-telegram-bot
that referenced
this pull request
Jul 1, 2026
…bility probe httpPreviewIsReachable() fetched any event-supplied previewUrl, including a UI API key header. An attacker-controlled previewUrl could target internal hosts (loopback, RFC1918, link-local, cloud metadata endpoints) and exfiltrate the SPARK_UI_API_KEY or probe the internal network. Adds isPrivateOrReservedHost() and short-circuits the probe before any fetch when the parsed hostname resolves to a private/reserved target. Beyond the original PR, the guard also covers IPv6 unique-local (fc00::/7), link-local (fe80::/10), and IPv4-mapped IPv6 (::ffff:a.b.c.d) per maintainer review. Delaminated from PR vibeforge1111#843: the bundled form-data 4.0.5->4.0.6 / hasown package-lock bump is unrelated and routed to dependabot tracking, not landed here. Note: this is a host-block (SSRF) guard. The complementary credential- confinement holes (UI key still sent to non-private external origins, vibeforge1111#118 / vibeforge1111#453) remain gated on security-owner sign-off and are not addressed here. Co-authored-by: yossweh <yossweh@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
[spark-compete] fix: validate previewUrl against private/reserved hosts before fetch
pr_author: yossweh
repo: vibeforge1111/spark-telegram-bot
branch: fix/ssrf-preview-url-validation
actual_behavior
httpPreviewIsReachable()insrc/missionRelay.ts:1238fetches URLs from relay eventpreviewUrl/preview_urlfields without validating the target host. ThenormalizePreviewLink()function only checks forlocalhost:5555and redirects those to project preview links, but passes through all other URLs unchanged — including internal network addresses like169.254.169.254(cloud metadata),10.x.x.x,172.16-31.x.x,192.168.x.x, and.internaldomains.A compromised relay secret or malicious Spawner UI could inject relay events with
previewUrl: "http://169.254.169.254/latest/meta-data/"causing the bot to make requests to cloud metadata endpoints, internal services, or other SSRF targets.expected_behavior
httpPreviewIsReachable()should validate the URL hostname against private/reserved IP ranges and internal domains before making the HTTP request. URLs targeting internal hosts should be rejected without fetching.public-safe proof of the exact failure
Before (current main):
After (this PR):
trust boundary touched by this change
Surface: Relay event
previewUrlfield → HTTP fetch inhttpPreviewIsReachable()Boundary change: Adds host validation before outbound HTTP request
What this does NOT change:
targeted tests / smoke checks
httpPreviewIsReachable("http://169.254.169.254/latest/meta-data/")falsehttpPreviewIsReachable("http://10.0.0.1:8080/admin")falsehttpPreviewIsReachable("http://192.168.1.1/")falsehttpPreviewIsReachable("http://metadata.google.internal/")falsehttpPreviewIsReachable("http://example.com/preview")npm run buildrisk notes
Which risky surface changed: Outbound HTTP fetch from relay events
Why the change is necessary: Prevents SSRF via malicious relay event previewUrl values
Secrets: No secrets introduced
Auth / session state: No changes to auth flow
Dependency / runtime behavior: No new deps; uses built-in URL constructor
File / network access: Restricts network access to non-internal hosts only
Prompt / tool execution: No changes
Rollback: Single-file revert
What reviewers / lab still need to verify: Test with real relay events to ensure legitimate preview URLs still work
duplicate_notes
Checked open PRs in spark-telegram-bot — no existing PR addresses SSRF in
httpPreviewIsReachable()or validatespreviewUrlagainst internal IP ranges. PR #295 is about Builder bridge graceful degradation (different issue). This fix adds a newisPrivateOrReservedHost()function specifically for the relay preview URL fetch path.review_claim
team
hellenagent (hellen, yossweh, exelchapo) — llm_device_holder: yossweh
packet
{ "schema": "spark-compete-hotfix-v1", "event": "spark-compete-first-event", "submission_mode": "public_repo_pr", "submission_target_url": "https://github.com/vibeforge1111/spark-telegram-bot/pull/843", "team": { "name": "hellenagent", "members": [ "hellen", "yossweh", "exelchapo" ], "github_accounts": [ "yossweh" ], "llm_device_holder": "yossweh", "device_holder_github": "yossweh" }, "target_repo": { "id": "vibeforge1111/spark-telegram-bot", "source": "https://github.com/vibeforge1111/spark-telegram-bot", "owner_surface": "telegram-bot" }, "issue": { "type": "bug", "title": "SSRF via unvalidated previewUrl in httpPreviewIsReachable", "severity": "medium", "affected_workflow": "relay event preview URL fetching", "actual_behavior": "httpPreviewIsReachable() fetches URLs from relay event previewUrl fields without validating against private/reserved IP ranges, allowing SSRF to cloud metadata, internal services, and loopback addresses", "expected_behavior": "URLs targeting private/reserved hosts should be rejected before making the HTTP request", "repro_steps": [ "Send relay event with previewUrl set to http://169.254.169.254/latest/meta-data/", "Observe httpPreviewIsReachable() makes request to cloud metadata endpoint", "No host validation is performed before fetch" ] }, "evidence": { "links": [ "https://github.com/vibeforge1111/spark-telegram-bot/pull/843" ], "forbidden": [ "do not include relay secrets", "do not include internal URLs" ], "safe_links_only": true, "before_after_proof": "Before: fetch(url) with no host validation. After: isPrivateOrReservedHost(parsed.hostname) check returns false for private IPs before fetch." }, "proposed_fix": { "approach": "Add isPrivateOrReservedHost() function that checks hostname against loopback, private ranges (10.x, 172.16-31.x, 192.168.x), link-local (169.254.x), cloud metadata endpoints, and .internal/.local domains. Call it in httpPreviewIsReachable() before fetch().", "files_expected": [ "src/missionRelay.ts" ], "tests_or_smoke": "Verify httpPreviewIsReachable returns false for 169.254.169.254, 10.0.0.1, 192.168.1.1, metadata.google.internal. Verify normal URLs still fetch. npm run build passes." }, "pr": { "url": "https://github.com/vibeforge1111/spark-telegram-bot/pull/843", "branch": "fix/ssrf-preview-url-validation", "title_prefix": "[spark-compete]", "author_github": "yossweh", "body_must_include": [ "packet", "team", "pr_author", "repo", "actual_behavior", "expected_behavior", "repro_steps", "before_after_proof", "tests_or_smoke", "duplicate_notes", "risk_notes", "review_claim" ] }, "review_claim": { "impact_claim": "medium", "evidence_types": [ "redacted_terminal_excerpt", "smoke_test", "redacted_conversation_excerpt" ], "review_state_requested": "pr_review", "duplicate_notes": "No existing PR addresses SSRF in httpPreviewIsReachable. PR #295 is about Builder bridge degradation (different issue).", "risk_notes": "Restricts outbound fetch to non-internal hosts. No new deps. Single-file change. Rollback: revert one commit." } }