mirror of
https://github.com/github/spec-kit.git
synced 2026-08-03 06:26:30 +08:00
harden: bound HTTP reads and enforce strict redirects (#3140)
* harden: bound HTTP reads and enforce strict redirects Add a shared _download_security module (read_response_limited, is_https_or_localhost_http, size constants) and route the GitHub release and Azure DevOps token network reads through bounded reads so an oversized response can't exhaust memory. Add a strict_redirects mode to authentication.open_url: the redirect handler now rejects any redirect whose target isn't HTTPS (or HTTP to localhost), composing with the existing per-hop redirect_validator and auth-stripping. The Azure DevOps token POST is routed through that handler so a 307/308 cannot forward the client_secret body to a non-HTTPS host. Assisted-by: Codex (model: GPT-5, autonomous) * test: align HTTP fakes with bounded reads Assisted-by: Codex (model: GPT-5, autonomous) * fix: tolerate invalid token response encoding Assisted-by: Codex (model: GPT-5, autonomous) * test: align GHES fakes with bounded reads Assisted-by: Codex (model: GPT-5, autonomous) * test: reuse shared upgrade HTTP response helper Assisted-by: Codex (model: GPT-5, autonomous) * fix: include rejected redirect target in error Assisted-by: Codex (model: GPT-5, autonomous) * fix: enforce strict redirects by default Assisted-by: Codex (model: GPT-5, autonomous) * fix: close redirect credential and SSRF gaps Assisted-by: Codex (model: GPT-5, autonomous)
This commit is contained in:
@@ -9,6 +9,7 @@ Tests cover:
|
||||
- Catalog stack (multi-catalog support)
|
||||
"""
|
||||
|
||||
import io
|
||||
import pytest
|
||||
import json
|
||||
import os
|
||||
@@ -22,6 +23,7 @@ from datetime import datetime, timezone
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from tests.conftest import strip_ansi
|
||||
from tests.http_helpers import route_opener_open_through_urlopen # noqa: F401
|
||||
from specify_cli import extensions as _ext_module
|
||||
from specify_cli.extensions import (
|
||||
CatalogEntry,
|
||||
@@ -4978,7 +4980,7 @@ class TestExtensionCatalog:
|
||||
zip_bytes = zip_buf.getvalue()
|
||||
|
||||
release_response = MagicMock()
|
||||
release_response.read.return_value = json.dumps(
|
||||
release_response.read.side_effect = io.BytesIO(json.dumps(
|
||||
{
|
||||
"assets": [
|
||||
{
|
||||
@@ -4987,12 +4989,12 @@ class TestExtensionCatalog:
|
||||
}
|
||||
]
|
||||
}
|
||||
).encode()
|
||||
).encode()).read
|
||||
release_response.__enter__ = lambda s: s
|
||||
release_response.__exit__ = MagicMock(return_value=False)
|
||||
|
||||
asset_response = MagicMock()
|
||||
asset_response.read.return_value = zip_bytes
|
||||
asset_response.read.side_effect = io.BytesIO(zip_bytes).read
|
||||
asset_response.__enter__ = lambda s: s
|
||||
asset_response.__exit__ = MagicMock(return_value=False)
|
||||
|
||||
@@ -8761,10 +8763,10 @@ def test_extension_wrapper_resolves_ghes_asset_when_host_configured(tmp_path, mo
|
||||
def fake_open(url, timeout=None, extra_headers=None):
|
||||
captured.append(url)
|
||||
resp = MagicMock()
|
||||
resp.read.return_value = json.dumps({
|
||||
resp.read.side_effect = io.BytesIO(json.dumps({
|
||||
"assets": [{"name": "ext.zip",
|
||||
"url": "https://ghes.example/api/v3/repos/o/r/releases/assets/7"}]
|
||||
}).encode()
|
||||
}).encode()).read
|
||||
yield resp
|
||||
|
||||
monkeypatch.setattr(catalog, "_open_url", fake_open)
|
||||
|
||||
Reference in New Issue
Block a user