mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-09-27 10:32:21 +02:00
fix(skills): harden skill import against DNS rebinding and SSRF TOCTOU (#5986)
* fix(skill-importer): validate URL scheme and improve skills.sh handling * fix(skill-importer): enhance DNS resolution and SSRF protection in fetch URL handling * fix(url-safety): add allowed_dist parameter to check_outbound_url for flexible private blocking * test(skill-importer): add comprehensive tests for URL parsing and outbound checks * ensure newline at end of file in test_check_outbound_url_allows_public_ip * fix(skill-importer): improve TLS certificate handling in _get_checked function * fix(skill-importer): enhance _check_fetch_url to handle both hostnames and full URLs * fix(skill-importer): enhance parse_skill_source to support skills.sh URLs in path and netloc * fix(skill-importer): simplify skills.sh hostname check in parse_skill_source * fix(skill-importer): enhance parse_skill_source to identify skills.sh URLs in path and handle localhost/IP addresses * fix(skill-importer): enhance _resolve_and_check_url to validate all resolved IP addresses and prevent TOCTOU vulnerabilities * fix(skill-importer): enhance parse_skill_source to support schemeless GitHub and skills.sh URLs * fix(memory): resolve CodeQL URL sanitization warning and restore _check_fetch_url test alias * fix(memory): pin skill fetch sockets without rewriting URLs * fix(memory): reject unsupported skill wrapper hosts * refactor(url-safety): remove unused importer exception * test(memory): keep redirect regression hermetic * test(dns-rebinding): add test for _PinnedTransport to ensure connection to pinned IP * fix(skill-importer): enhance skills.sh support to extract GitHub links from page content * fix(skill-importer): improve URL scheme validation for GitHub and skills.sh links * fix(skills): reject unusable skill URLs instead of guessing Resolving a skills.sh link by scraping the first github.com URL out of the page body cannot work. Skill pages only ever link the repository root, never the skill's subdirectory, so every skill in a repo resolved to the same bundle: importing skills.sh/anthropics/skills/pdf walked the whole monorepo, saturated the 64-file cap, and installed algorithmic-art behind an ok:true response. Restore the redirect-target unwrap and fail with a message that says what to do instead. Also report the real reason a URL is rejected. The scheme check keyed off "://" appearing anywhere in the string, so a supplied-but-unusable URL came back as "URL is required", and a schemeless URL carrying "://" in its query was reported as an unsupported scheme. Key off the parsed scheme and let opaque schemes (mailto:, javascript:) and a schemeless host:port fall through to the host check. * test(skills): tighten the real-socket pinning regression The handler swallowed its own exceptions, so a failure inside it surfaced as a confusing assertion on the captured client address. Record the exception and assert on it, run the thread as a daemon, and close the listening socket from the test so a hang cannot outlive the run. Also drop the duplicate ipaddress import and the missing newline. * fix(skills): require exact GitHub skill URLs * test(skills): read complete pinned request headers --------- Co-authored-by: RaresKeY <158580472+RaresKeY@users.noreply.github.com> Co-authored-by: Léo <leograndcontact@gmail.com>
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
"""Skill URL importer — GitHub path parsing."""
|
||||
import ipaddress
|
||||
|
||||
import pytest
|
||||
|
||||
from services.memory.skill_importer import (
|
||||
@@ -11,6 +13,13 @@ from services.memory.skill_importer import (
|
||||
)
|
||||
|
||||
|
||||
def _allow_fetch(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
"services.memory.skill_importer._resolve_and_check_url",
|
||||
lambda url: [ipaddress.ip_address("93.184.216.34")],
|
||||
)
|
||||
|
||||
|
||||
def test_parse_github_blob_skill_md():
|
||||
src = parse_skill_source(
|
||||
"https://github.com/anthropics/skills/blob/main/skills/pdf/SKILL.md"
|
||||
@@ -69,10 +78,7 @@ def test_fetch_bytes_rejects_cross_host_redirect(monkeypatch):
|
||||
return _Resp()
|
||||
|
||||
monkeypatch.setattr("services.memory.skill_importer.httpx.Client", _Client)
|
||||
monkeypatch.setattr(
|
||||
"services.memory.skill_importer.check_outbound_url",
|
||||
lambda url, **kwargs: (True, ""),
|
||||
)
|
||||
_allow_fetch(monkeypatch)
|
||||
with pytest.raises(SkillImportError, match="redirect target"):
|
||||
_fetch_bytes("https://raw.githubusercontent.com/o/r/main/SKILL.md")
|
||||
|
||||
@@ -89,10 +95,7 @@ def test_list_github_dir_accepts_api_github_response(monkeypatch):
|
||||
"services.memory.skill_importer._fetch_text",
|
||||
lambda url: "# skill\n",
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"services.memory.skill_importer.check_outbound_url",
|
||||
lambda url, **kwargs: (True, ""),
|
||||
)
|
||||
_allow_fetch(monkeypatch)
|
||||
|
||||
class _Resp:
|
||||
url = "https://api.github.com/repos/o/r/contents?ref=main"
|
||||
@@ -144,10 +147,7 @@ def _mock_httpx_client(monkeypatch, response):
|
||||
return response
|
||||
|
||||
monkeypatch.setattr("services.memory.skill_importer.httpx.Client", _Client)
|
||||
monkeypatch.setattr(
|
||||
"services.memory.skill_importer.check_outbound_url",
|
||||
lambda url, **kwargs: (True, ""),
|
||||
)
|
||||
_allow_fetch(monkeypatch)
|
||||
|
||||
|
||||
def test_list_github_dir_surfaces_rate_limit(monkeypatch):
|
||||
|
||||
Reference in New Issue
Block a user