Files
hermes-agent/tests/pm/test_update_auth_scope.py
ethernet 1c8fae6180 fix(pm): preserve runtime and user state across failure paths
Keep downloads bound to their remote representation and publish through
atomic destination-local staging. Serialize shared partial ownership.

Keep explicit CA trust scoped to provider probes. Preserve checkpoint
history and edited files, validate all profile inputs before dependency
publication, and separate data removal from installed runtime ownership.

Exclude machine-specific PM state from portable transfers. Keep plugin
files and nested skill tools intact. Preserve native test isolation.

Focused native Windows receipts cover the individual repairs and their
integration. This commit does not claim a full-suite or release build.
2026-09-09 15:17:08 -04:00

78 lines
3.1 KiB
Python

"""Index credentials stay on their exact HTTPS origin, including redirects."""
from __future__ import annotations
from email.message import Message
import io
import urllib.request
import urllib.response
import pytest
from pm import update
class IndexTransport(urllib.request.HTTPHandler, urllib.request.HTTPSHandler):
def __init__(self, redirects=None):
super().__init__()
self.redirects = redirects or {}
self.sent = []
def http_open(self, request):
self.sent.append((request.full_url, dict(request.header_items())))
headers = Message()
destination = self.redirects.get(request.full_url)
if destination:
headers["Location"] = destination
response = urllib.response.addinfourl(
io.BytesIO(b'{}'), headers, request.full_url, 302 if destination else 200,
)
response.msg = "Found" if destination else "OK"
return response
https_open = http_open
@pytest.mark.parametrize("url,token", [
("https://api.github.com/repos/example/tool/releases", "dummy-gh"),
("https://api.github.com:443/repos/example/tool/releases", "dummy-gh"),
("https://huggingface.co/api/buckets/example/tool/tree", "dummy-hf"),
("https://nodejs.org/dist/index.json", None),
("https://registry.npmjs.org/npm", None),
("https://evilhuggingface.co/index", None),
("https://api.github.com.evil.invalid/index", None),
("https://api.github.com:444/index", None),
("http://api.github.com/index", None),
])
def test_index_credentials_match_the_exact_origin(monkeypatch, url, token):
transport = IndexTransport()
monkeypatch.setattr(urllib.request, "_opener", urllib.request.build_opener(transport))
monkeypatch.setenv("GH_TOKEN", "dummy-gh")
monkeypatch.setenv("HF_TOKEN", "dummy-hf")
assert update._get_json(url) == {}
headers = {key.lower(): value for key, value in transport.sent[-1][1].items()}
assert headers.get("authorization") == (f"Bearer {token}" if token else None)
assert headers["user-agent"] == "hermes-pm"
@pytest.mark.parametrize("reader", [update._get_json, update._get_text], ids=["json", "text"])
@pytest.mark.parametrize("destination,credential_survives", [
("https://api.github.com/second", True),
("https://unrelated.invalid/second", False),
("https://api.github.com:444/second", False),
("http://api.github.com/second", False),
])
def test_index_redirects_use_the_credential_safe_opener(monkeypatch, reader, destination, credential_survives):
origin = "https://api.github.com/first"
transport = IndexTransport({origin: destination})
monkeypatch.setattr(urllib.request, "_opener", urllib.request.build_opener(transport))
monkeypatch.setenv("GH_TOKEN", "dummy-gh")
reader(origin)
assert [url for url, _ in transport.sent] == [origin, destination]
initial, redirected = [{key.lower(): value for key, value in headers.items()} for _, headers in transport.sent]
assert initial["authorization"] == "Bearer dummy-gh"
assert (redirected.get("authorization") == "Bearer dummy-gh") is credential_survives