mirror of
https://github.com/usestrix/strix.git
synced 2026-09-05 08:06:08 +00:00
* feat(cli): add strix login for managed platform sign-in (device flow) * feat(cli): add --scopes flag to strix login * docs: document strix login and managed billing in README, AGENTS, docs, and managed skill * fix(cli): handle malformed login responses and credential file failures * fix(cli): reject sign-in responses without an API token * feat(login): interactive workspace and scope selection with presets * fix(login): reject malformed API token values in sign-in responses * fix(login): skip the scope prompt when stdin is not a terminal * fix(login): tolerate malformed selection containers and remove unreadable credential files on logout * fix(login): treat overflowing timing values as invalid * fix(login): show the configured platform host in the sign-in banner * fix(login): bound device flow timing values and clean up unreplaced secret temp files * feat(cli): add the strix cloud command surface for the managed platform * feat(cli): manage workspaces and hosted onboarding links from strix cloud * fix(cli): report a leftover temporary secret file instead of hiding it * feat(cli): pass a Stripe payment method to the top-up wallet client * docs(cloud): recommend the Stripe agent wallet as the default payment path * fix(cloud): preserve API auth during MPP payment * fix(cloud): drop knowledge query and settings commands removed from the API * fix(cloud): align agent commands with API contracts * fix(cloud): send required PR review integration fields * fix(cloud): preserve scopes when switching workspaces * fix(cloud): make session command help non-destructive * feat(cloud): improve human navigation and output * feat(cli): add native shell completions * feat(cloud): tailor human list and detail views * feat(cloud): upload local source for managed scans * fix(cloud): infer scan type from local targets * Add agent-friendly managed cloud CLI * Harden cloud CLI type boundaries * Clarify cloud test user MFA options * Correct cloud vulnerability status guidance * Clarify chat file path handling * Allow signed storage upload URLs * Fix provider token request handling * Improve cloud CLI human list views * Make cloud CLI workflows actionable and safe * Make cloud workspace switching session-safe * Preserve CLI session metadata in JSON output * Remove preview protection bypass plumbing from cloud CLI
136 lines
4.6 KiB
Python
136 lines
4.6 KiB
Python
"""Security tests for the wallet payment loopback bridge."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import urllib.error
|
|
import urllib.request
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
import pytest
|
|
|
|
from strix.interface.cloud import payment_proxy
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Iterator
|
|
|
|
|
|
class _StreamingResponse:
|
|
status_code = 200
|
|
|
|
def __init__(self, chunks: list[bytes]) -> None:
|
|
self.chunks = chunks
|
|
self.closed = False
|
|
self.headers = {"Content-Type": "application/json"}
|
|
|
|
def iter_content(self, *, chunk_size: int) -> Iterator[bytes]:
|
|
assert chunk_size > 0
|
|
yield from self.chunks
|
|
|
|
def close(self) -> None:
|
|
self.closed = True
|
|
|
|
|
|
def _post(url: str, body: bytes, headers: dict[str, str] | None = None) -> bytes:
|
|
request = urllib.request.Request( # noqa: S310
|
|
url,
|
|
data=body,
|
|
headers={"Content-Type": "application/json", **(headers or {})},
|
|
method="POST",
|
|
)
|
|
with urllib.request.urlopen(request, timeout=2) as response: # noqa: S310
|
|
return response.read()
|
|
|
|
|
|
def test_bridge_bounds_decompressed_upstream_response(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
response = _StreamingResponse([b"1234", b"5"])
|
|
|
|
def fake_request(*_args: Any, **kwargs: Any) -> _StreamingResponse:
|
|
assert kwargs["stream"] is True
|
|
return response
|
|
|
|
monkeypatch.setattr(payment_proxy, "_MAX_UPSTREAM_RESPONSE_BYTES", 4)
|
|
monkeypatch.setattr(payment_proxy.requests, "request", fake_request)
|
|
|
|
with payment_proxy.wallet_payment_bridge(
|
|
upstream_url="https://app.example.test/api/v1/billing/topup",
|
|
api_token="strix-secret", # noqa: S106
|
|
expected_body=b"{}",
|
|
) as wallet_url:
|
|
request = urllib.request.Request( # noqa: S310
|
|
wallet_url,
|
|
data=b"{}",
|
|
headers={"Content-Type": "application/json"},
|
|
method="POST",
|
|
)
|
|
with pytest.raises(urllib.error.HTTPError) as exc_info:
|
|
urllib.request.urlopen(request, timeout=2) # noqa: S310
|
|
|
|
assert exc_info.value.code == 502
|
|
assert response.closed is True
|
|
|
|
|
|
def test_bridge_forwards_only_the_approved_request_and_protected_headers(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
captured: list[dict[str, Any]] = []
|
|
observed: list[payment_proxy.WalletUpstreamResponse] = []
|
|
|
|
def fake_request(*_args: Any, **kwargs: Any) -> _StreamingResponse:
|
|
captured.append(kwargs)
|
|
return _StreamingResponse([b'{"ok":true}'])
|
|
|
|
monkeypatch.setattr(payment_proxy.requests, "request", fake_request)
|
|
with payment_proxy.wallet_payment_bridge(
|
|
upstream_url="https://app.example.test/api/v1/billing/topup",
|
|
api_token="strix-secret", # noqa: S106
|
|
expected_body=b'{"credits":5}',
|
|
response_observer=observed.append,
|
|
) as wallet_url:
|
|
result = _post(
|
|
wallet_url,
|
|
b'{"credits":5}',
|
|
{
|
|
"Authorization": "Payment wallet-proof",
|
|
"Proxy-Authorization": "Basic drop-me",
|
|
"X-Strix-Authorization": "Bearer attacker",
|
|
},
|
|
)
|
|
|
|
assert result == b'{"ok":true}'
|
|
headers = captured[0]["headers"]
|
|
assert headers["Authorization"] == "Payment wallet-proof"
|
|
assert headers["X-Strix-Authorization"] == "Bearer strix-secret"
|
|
assert "Proxy-Authorization" not in headers
|
|
assert not any(name.lower() in {"host", "content-length"} for name in headers)
|
|
assert observed == [
|
|
payment_proxy.WalletUpstreamResponse(status_code=200, body=b'{"ok":true}')
|
|
]
|
|
|
|
with pytest.raises(urllib.error.HTTPError) as wrong_body:
|
|
_post(wallet_url, b'{"credits":500}')
|
|
assert wrong_body.value.code == 403
|
|
assert len(captured) == 1
|
|
|
|
|
|
def test_bridge_allows_only_two_valid_wallet_attempts(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
calls = 0
|
|
|
|
def fake_request(*_args: Any, **_kwargs: Any) -> _StreamingResponse:
|
|
nonlocal calls
|
|
calls += 1
|
|
return _StreamingResponse([b"{}"])
|
|
|
|
monkeypatch.setattr(payment_proxy.requests, "request", fake_request)
|
|
with payment_proxy.wallet_payment_bridge(
|
|
upstream_url="https://app.example.test/api/v1/billing/topup",
|
|
api_token="strix-secret", # noqa: S106
|
|
expected_body=b"{}",
|
|
) as wallet_url:
|
|
assert _post(wallet_url, b"{}") == b"{}"
|
|
assert _post(wallet_url, b"{}") == b"{}"
|
|
with pytest.raises(urllib.error.HTTPError) as third_request:
|
|
_post(wallet_url, b"{}")
|
|
|
|
assert third_request.value.code == 429
|
|
assert calls == 2
|