Skip to content

Commit 7cdbd46

Browse files
committed
feat: batch 3 — conftest fixtures, CLI tests, py.typed marker
- Shared conftest.py with MockProvider, default_policy, pop_client fixtures - CLI smoke tests for pop-launch --help and module imports - py.typed PEP 561 marker for type checker support - CONTRIBUTING.md updated for Playwright/security features
1 parent 336ad1d commit 7cdbd46

File tree

4 files changed

+77
-4
lines changed

4 files changed

+77
-4
lines changed

CONTRIBUTING.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ Guardrails are the "brains" that decide whether a payment should be approved or
2525

2626
### Browser Injector (Autonomous Fulfillment)
2727
For agent frameworks evaluating DOMs, Point One Percent autonomously fulfills authorized payments without leaking the card directly to the LLM. Once the policy and guardrails approve a request, the injection and submission happen without any per-transaction human confirmation.
28-
- **PopBrowserInjector**: Connects strictly out-of-band via CDP (`Chrome DevTools Protocol`). Traverses cross-origin iframes (i.e. Stripe Elements) and auto-populates `<input>` and `<select>` elements safely. After injection, the agent clicks the submit button — this is a standard browser interaction, not a security concern, since card credentials are never in the agent's context.
28+
- **PopBrowserInjector**: Connects out-of-band via Playwright's `connectOverCDP` (not raw WebSocket). Traverses cross-origin iframes (including Stripe Elements sandboxed iframes) and Shadow DOM trees, auto-populating `<input>` and `<select>` elements safely. After injection, the agent clicks the submit button — card credentials are never in the agent's context.
29+
- **PAN encryption at rest**: Card data stored in the state tracker is encrypted with AES-256-GCM. The encryption key is derived from `POP_STATE_ENCRYPTION_KEY` env var or a hostname-based HMAC fallback.
30+
- **Security scan**: Every page is scanned for hidden prompt-injection elements before card injection proceeds.
2931
- **Chrome must be launched with `--remote-debugging-port=9222`** before the injector can attach. Use `--user-data-dir` as well if Chrome is already running (required to open a separate CDP-enabled instance).
3032
- **When using Playwright MCP** (e.g., with Claude Code), configure it with `--cdp-endpoint http://localhost:9222` so that both Playwright MCP and Point One Percent MCP share the same Chrome instance. See [docs/INTEGRATION_GUIDE.md §1](./docs/INTEGRATION_GUIDE.md#1-claude-code--full-setup-with-cdp-injection) for the full setup.
3133

@@ -91,11 +93,11 @@ Based on real-world agent testing, two observability gaps have been identified:
9193
- **Billing field confirmation**: When `PopBrowserInjector` auto-fills billing fields (name, address, email), the agent has no way to confirm what was filled without taking a screenshot. The `request_virtual_card` MCP tool should return a summary of which fields were filled and with what values (excluding the card number itself).
9294
- **Injection failure transparency**: If card field injection fails (e.g. payment form not found, iframe traversal issue), the MCP tool currently returns a generic error. More granular failure codes would help agents diagnose and report the correct remediation to users.
9395

94-
### 5. CDP Injection Resilience
95-
The `PopBrowserInjector` handles most common checkout forms and cross-origin Stripe iframes. Contributions are welcome for:
96-
- Shadow DOM traversal (web components used by some payment processors)
96+
### 5. Injection Resilience
97+
The `PopBrowserInjector` uses Playwright's `connectOverCDP` for cross-origin iframe traversal and includes Shadow DOM piercing. Contributions are welcome for:
9798
- Dynamic form detection (forms that render after JS load with non-standard field naming)
9899
- Automated test fixtures covering more real-world checkout page structures
100+
- Additional `<select>` dropdown handling for country/state pickers
99101

100102
### 6. Security: Opaque Agent Responses
101103

pop_pay/py.typed

Whitespace-only changes.

tests/conftest.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""Shared fixtures for pop-pay test suite."""
2+
import uuid
3+
import pytest
4+
from pop_pay.client import PopClient
5+
from pop_pay.core.models import PaymentIntent, GuardrailPolicy, VirtualSeal
6+
from pop_pay.providers.base import VirtualCardProvider
7+
8+
9+
class MockProvider(VirtualCardProvider):
10+
"""Provider that always issues a card (no real network call)."""
11+
12+
async def issue_card(self, intent: PaymentIntent, policy: GuardrailPolicy) -> VirtualSeal:
13+
return VirtualSeal(
14+
seal_id=str(uuid.uuid4()),
15+
card_number="1234567812345678",
16+
cvv="123",
17+
expiration_date="12/26",
18+
authorized_amount=intent.requested_amount,
19+
status="Issued",
20+
)
21+
22+
23+
@pytest.fixture
24+
def mock_provider():
25+
return MockProvider()
26+
27+
28+
@pytest.fixture
29+
def default_policy():
30+
return GuardrailPolicy(
31+
allowed_categories=["cloud", "aws", "openai"],
32+
max_amount_per_tx=100.0,
33+
max_daily_budget=500.0,
34+
block_hallucination_loops=True,
35+
)
36+
37+
38+
@pytest.fixture
39+
def pop_client(mock_provider, default_policy):
40+
client = PopClient(mock_provider, default_policy, db_path=":memory:")
41+
yield client
42+
client.state_tracker.close()

tests/test_cli.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"""CLI smoke tests for pop-pay entry points."""
2+
import subprocess
3+
import sys
4+
5+
6+
def _run(cmd: list[str], timeout: float = 10) -> subprocess.CompletedProcess:
7+
return subprocess.run(
8+
cmd, capture_output=True, text=True, timeout=timeout
9+
)
10+
11+
12+
def test_pop_launch_help():
13+
result = _run([sys.executable, "-m", "pop_pay.cli", "--help"])
14+
assert result.returncode == 0
15+
assert "pop-launch" in result.stdout or "usage" in result.stdout.lower()
16+
17+
18+
def test_cli_vault_importable():
19+
"""cli_vault module imports without error."""
20+
result = _run([sys.executable, "-c", "from pop_pay.cli_vault import cmd_init_vault; print('ok')"])
21+
assert result.returncode == 0
22+
assert "ok" in result.stdout
23+
24+
25+
def test_cli_unlock_importable():
26+
"""cli_unlock module imports without error."""
27+
result = _run([sys.executable, "-c", "from pop_pay.cli_unlock import cmd_unlock; print('ok')"])
28+
assert result.returncode == 0
29+
assert "ok" in result.stdout

0 commit comments

Comments
 (0)