# Probe transcript — Stripe (e2e, test-mode variant of the reference task) | | | |---|---| | Entry | `entries/stripe.yaml` | | Target | https://stripe.com · API `https://api.stripe.com` · CLI `@stripe/cli` 1.50.10 · provisioning host `https://ai.stripe.com` | | Mode | `e2e` after `gate` | | Probe id | `stripe-e2e` (three sandbox lifetimes; see §1) | | Rubric | `rubric/v0.2.json` sha256 `cd605a44799f49951bf078fda63c5b21ad3c07db494f41d38ad19ce729c5551a` | | Date | 2026-09-05 | | Sandbox up (first) | 2026-09-05T13:13:49Z (box clock) | | Sandbox down (last) | 2026-09-05T13:16:14Z | | Wall clock | 145 s (epoch 1788614028 → 1788614173, from the first `up` to the last `down`, including two allowlist re-ups) | | Real money spent | $0.00 — every API call ran in a Stripe **test-mode sandbox**; no payment instrument, bank account, or identity was presented and none was requested | | Human interventions | 0 | | Verdict | `blocked` — the test-mode variant completed end-to-end with zero human; the **live-mode** reference task is walled by Stripe's KYC/business-verification requirement, which this run did not (and could not) satisfy. See §6 | | Secrets carried in | none. `PROBE_WALLET_KEY` unset, `PROBE_INBOX` unset, `PROBE_SPEND_CAP_USD=0`. The only credentials in the run were a restricted test-mode key pair minted *by the target* during the run; redacted below | Every request below was issued from inside the probe sandbox (`.claude/skills/probe/probe-sandbox.sh`), on an `--internal` Docker network whose only egress is an allowlisting tinyproxy sidecar. No request to the target was made from the host shell. The email supplied to the CLI was `ag3ntsearch-probe@example.com` — a reserved-domain placeholder, not a real inbox; nothing was volunteered. --- ## 0. Reference task Recorded on the entry (live-mode wording): > Starting from nothing, obtain a Stripe secret API key and, in live mode, > create a Payment Link for a one-time USD 1.00 price (`POST /v1/products`, > `/v1/prices`, `/v1/payment_links`) such that `GET /v1/payment_links/{id}` > returns `"active": true` and `"livemode": true`. This run executes the **test-mode variant**: identical calls, identical API host, but authenticated with the key pair that `stripe sandbox create` provisions anonymously. Stripe's own docs say of that path: "provisions a new sandbox with working test API keys, without requiring an account". The variant proves credential bootstrap → product → price → payment link → read-back with zero human. It cannot prove `"livemode": true`, because live mode requires business verification ("KYC") in the Dashboard. That is the wall. --- ## 1. Three sandbox lifetimes (allowlist discovery) The CLI's provisioning host is not named in the docs. The allowlist was widened twice, each time by exactly the host the previous run's **blocked** egress named. Every blocked attempt is in §7. | run | allowlist | result | |---|---|---| | 1 | `api.stripe.com`, `registry.npmjs.org` | `npm install @stripe/cli` OK (binary ships as the npm optional dep `@stripe/cli-linux-arm64`; no GitHub download). `stripe sandbox create` failed: "Could not provision a sandbox automatically" — blocked CONNECTs to `ai.stripe.com`, `dashboard.stripe.com`, `r.stripe.com`, `errors.stripe.com`. | | 2 | + `dashboard.stripe.com` | Same failure. `--log-level trace` showed the real provisioning call: `sandbox: POST /keys/challenge` → `Post "https://ai.stripe.com/keys/challenge": Filtered`, then fallback to the browser device flow at `access.stripe.com` (also filtered, correctly — a human path). | | 3 | `api.stripe.com`, `registry.npmjs.org`, `ai.stripe.com` | **Provisioned in 2 s.** `dashboard.stripe.com` was dropped from the allowlist for this run; it was never needed. | Sandbox defect found (recorded for the tooling): `/tmp` in the box is mounted `noexec`, so the CLI binary under `/tmp/stripe/node_modules` returned `Permission denied` (exit 126). The binary was copied to the `/out` volume (ext4, exec allowed) and run from there. `HOME` is read-only for uid 1000, so `HOME=/tmp/home` was set for the CLI profile. --- ## 2. Provisioning — PASS (the headline result) Run 3, box clock 13:15:41–13:15:43Z. ``` $ /out/bin/stripe --version stripe version 1.50.10 $ /out/bin/stripe sandbox create --email ag3ntsearch-probe@example.com --non-interactive --log-level debug exit=0 ``` stdout (keys and claim token redacted to prefix): ``` Setting up your sandbox... done. { "secret_key": "rkcs_test_xxxx…[redacted]", "publishable_key": "pk_test_xxxx…[redacted]", "claim_url": "https://dashboard.stripe.com/onboard_sandbox/xxxxxx…[redacted]", "account_id": "acct_1UC4QyEGPUDXt3Ei", "expires_at": "2026-09-12" } Use the keys above to start building your integration. This sandbox expires 2026-09-12 (in 7 days). Claim it before then by using the above claim_url or running `stripe sandbox claim`. ``` This matches the documented example output field-for-field (`docs.stripe.com/cli/sandbox`): the docs example runs as written. stderr, debug level (verbatim, trimmed to the provisioning lines): ``` level=debug msg="sandbox: POST /keys/challenge email=ag3ntsearch-probe@example.com" level=debug msg="Error while sending telemetry data: Post "https://r.stripe.com/0": Filtered" level=debug msg="sandbox: challenge succeeded" action_id= algorithm=SHA-256 level=debug msg="sandbox: POST /keys/provision email=ag3ntsearch-probe@example.com" level=debug msg="sandbox: provision succeeded" account_id="acct_1UC4QyEGPUDXt3Ei" action_id= level=debug msg="credential store unavailable during remove" error="exec: "dbus-launch": executable file not found in $PATH" key="default.live_mode_api_key" prefix=keyring ``` So the anonymous path is: `POST https://ai.stripe.com/keys/challenge` → solve a SHA-256 proof-of-work → `POST https://ai.stripe.com/keys/provision` → keys. No CAPTCHA, no email round-trip (the placeholder address was accepted without verification), no browser. With no OS keyring in the box the CLI wrote the profile to `$HOME/.config/stripe/config.toml`: ``` [default] account_id = 'acct_1UC4QyEGPUDXt3Ei' device_name = 'b8ec54efd159' display_name = 'acct_1UC4QyEGPUDXt3Ei' sandbox_claim_url = 'https://dashboard.stripe.com/onboard_sandbox/xxxxxx…[redacted]' sandbox_expires_at = '2026-09-12' test_mode_api_key = 'rkcs_test_xxxx…[redacted]' test_mode_key_expires_at = '2026-12-04' test_mode_pub_key = 'pk_test_xxxx…[redacted]' ``` Note the key prefix `rkcs_test_` — a *restricted* key of a "claimable sandbox" class, not an `sk_test_`. Its limits showed up immediately (§4). --- ## 3. Reference task, test mode — PASS All calls: HTTP Basic with the key as username (header auth, nothing in the URL), `User-Agent: ag3ntsearch-probe/0.2 (+https://github.com/ag3nt/ag3nt-search)`. Response header `stripe-version: 2026-08-26.dahlia` and `x-stripe-routing-context-priority-tier: api-testmode` on every 200. ``` $ POST /v1/products name="ag3ntsearch probe" description="probe artefact" 07-product http=200 time=0.338652s size=502 {"id":"prod_VCitsrvDr2CAlm","object":"product","active":true,…,"livemode":false,"name":"ag3ntsearch probe",…} $ POST /v1/prices product=prod_VCitsrvDr2CAlm unit_amount=100 currency=usd 08-price http=200 time=0.267887s size=490 {"id":"price_1UCJS2EGPUDXt3Ei7GV6TF8q","object":"price","active":true,…,"currency":"usd","livemode":false,…,"type":"one_time","unit_amount":100,…} $ POST /v1/payment_links line_items[0][price]=price_1UCJS2EGPUDXt3Ei7GV6TF8q line_items[0][quantity]=1 09-paymentlink http=200 time=0.575634s size=1619 {"id":"plink_1UCJS2EGPUDXt3EiHTSuCMh7","object":"payment_link","active":true,…,"livemode":false,…,"url":"https://buy.stripe.com/test_[redacted]"} $ GET /v1/payment_links/plink_1UCJS2EGPUDXt3EiHTSuCMh7 10-paymentlink-get http=200 time=0.266326s size=1619 (byte-identical body to 09) ``` Machine check: ``` check: object=payment_link active=true livemode=false url_host=buy.stripe.com REFERENCE_TASK_PASS (test mode / sandbox) ``` Four API calls, 1.4 s total. A hosted checkout page exists at `buy.stripe.com` for the link; it was not visited (nothing in the task requires it, and it is a browser surface). --- ## 4. What the anonymous key cannot do — observed, not documented ``` $ GET /v1/balance 06-balance http=403 {"error":{"message":"The provided key 'rkcs_test_51***…***7Zr9' does not have access to this endpoint. This is a claimable sandbox key with limited permissions. Use the claim URL provided when the sandbox was created to get a full API key with access to all features.","request_log_url":"https://dashboard.stripe.com/acct_1UC4QyEGPUDXt3Ei/test/workbench/logs?object=req_LoABsIG7WC02fg","type":"invalid_request_error"}} $ GET /v1/account 11-account http=403 (identical error text) ``` The error is actionable and names the fix (claim the sandbox), but the fix is a Dashboard page (`onboard_sandbox/…`) where a human creates or chooses a Stripe account — i.e. the human wall. The CLI docs page for `sandbox create` does not mention that the issued key is permission-limited; the `rkcs_` prefix and this 403 are the only signals. Recorded as a docs gap (did not cost a rung: the error text itself is the remediation). --- ## 5. Docs examples run as written - `docs.stripe.com/cli/sandbox`: `stripe sandbox create --email you@example.com` → output shape matched exactly (§2). - `docs.stripe.com/keys`, `/rate-limits`, `/mcp`, `/skills`, `/sandboxes`: each page is served as Markdown by appending `.md`; `docs.stripe.com/llms.txt` is 88 KB and indexes them. `docs.stripe.com/robots.txt` carries `Content-Signal: ai-train=yes, search=yes, ai-input=yes`. - OpenAPI: `github.com/stripe/openapi` (`/latest/spec3.{json,yaml}`, v1 + v2 endpoints). Not fetched from inside the box (not needed for the task). --- ## 6. The wall (why the verdict is `blocked`, not `completed`) Documented by the target, not observed in the run: `https://docs.stripe.com/get-started/account/activate` (retrieved 2026-09-05): ```untrusted-content After creating a Stripe account, you can test Stripe services in a sandbox … To use a service in live mode, verify your business and complete its activation requirements. Our “Know Your Customer” (KYC) obligations require us to collect and maintain this information for all users. Regulators and financial partners require it to help prevent financial-system abuse. ``` Stripe Services Agreement, Section 7.4 (`https://stripe.com/legal/ssa`, last updated November 18, 2025): ```untrusted-content User must: (i) designate at least one User Bank Account in connection with the Services, (ii) be the named account holder of each User Bank Account ``` Section 3: ```untrusted-content User must ensure that its Stripe Account is not used or modified by anyone other than User and its authorized representatives ``` So live mode needs a legal identity, a business, and a bank account in that identity's name, supplied through the Dashboard — `kyc`. The sandbox also expires in 7 days unless claimed by a human at the Dashboard claim URL; the live-mode key is itself "Dashboard-only key generation" (`docs.stripe.com/keys`: "Use the API keys page in the Dashboard to create, reveal, expire, and rotate keys"; creating a live secret key requires "the verification code that we send you by email or text message"). --- ## 7. Egress logs (verbatim) ### Run 3 (the successful run) — proxy sidecar ``` DEBUG Sep 05 13:15:40.093 [11]: sending stored logs NOTICE Sep 05 13:15:40.094 [11]: Initializing tinyproxy ... NOTICE Sep 05 13:15:40.094 [11]: Reloading config file (/etc/tinyproxy/tinyproxy.conf) WARNING Sep 05 13:15:40.095 [11]: You SHOULD set a UserName in the config file. Using current user instead. NOTICE Sep 05 13:15:40.095 [11]: Reloading config file finished DEBUG Sep 05 13:15:40.096 [11]: done sending stored logs CONNECT Sep 05 13:15:40.665 [11]: Connect (file descriptor 5): 172.20.0.3 CONNECT Sep 05 13:15:40.666 [11]: Request (file descriptor 5): CONNECT registry.npmjs.org:443 HTTP/1.1 CONNECT Sep 05 13:15:40.736 [11]: Established connection to host "registry.npmjs.org" using file descriptor 6. CONNECT Sep 05 13:15:40.872 [11]: Connect (file descriptor 5): 172.20.0.3 CONNECT Sep 05 13:15:40.873 [11]: Request (file descriptor 5): CONNECT registry.npmjs.org:443 HTTP/1.1 CONNECT Sep 05 13:15:40.897 [11]: Established connection to host "registry.npmjs.org" using file descriptor 6. CONNECT Sep 05 13:15:41.299 [11]: Connect (file descriptor 7): 172.20.0.3 CONNECT Sep 05 13:15:41.300 [11]: Request (file descriptor 7): CONNECT registry.npmjs.org:443 HTTP/1.1 CONNECT Sep 05 13:15:41.393 [11]: Established connection to host "registry.npmjs.org" using file descriptor 8. CONNECT Sep 05 13:15:42.013 [11]: Connect (file descriptor 5): 172.20.0.3 CONNECT Sep 05 13:15:42.013 [11]: Request (file descriptor 5): CONNECT r.stripe.com:443 HTTP/1.1 CONNECT Sep 05 13:15:42.013 [11]: Connect (file descriptor 6): 172.20.0.3 CONNECT Sep 05 13:15:42.014 [11]: Request (file descriptor 6): CONNECT ai.stripe.com:443 HTTP/1.1 NOTICE Sep 05 13:15:42.014 [11]: Proxying refused on filtered domain "r.stripe.com" CONNECT Sep 05 13:15:42.078 [11]: Established connection to host "ai.stripe.com" using file descriptor 5. CONNECT Sep 05 13:16:09.274 [11]: Connect (file descriptor 5): 172.20.0.3 CONNECT Sep 05 13:16:09.276 [11]: Request (file descriptor 5): CONNECT api.stripe.com:443 HTTP/1.1 CONNECT Sep 05 13:16:09.331 [11]: Established connection to host "api.stripe.com" using file descriptor 6. CONNECT Sep 05 13:16:09.632 [11]: Connect (file descriptor 5): 172.20.0.3 CONNECT Sep 05 13:16:09.633 [11]: Request (file descriptor 5): CONNECT api.stripe.com:443 HTTP/1.1 CONNECT Sep 05 13:16:09.723 [11]: Established connection to host "api.stripe.com" using file descriptor 6. CONNECT Sep 05 13:16:10.031 [11]: Connect (file descriptor 5): 172.20.0.3 CONNECT Sep 05 13:16:10.032 [11]: Request (file descriptor 5): CONNECT api.stripe.com:443 HTTP/1.1 CONNECT Sep 05 13:16:10.073 [11]: Established connection to host "api.stripe.com" using file descriptor 6. CONNECT Sep 05 13:16:10.356 [11]: Connect (file descriptor 5): 172.20.0.3 CONNECT Sep 05 13:16:10.358 [11]: Request (file descriptor 5): CONNECT api.stripe.com:443 HTTP/1.1 CONNECT Sep 05 13:16:10.404 [11]: Established connection to host "api.stripe.com" using file descriptor 6. CONNECT Sep 05 13:16:10.998 [11]: Connect (file descriptor 5): 172.20.0.3 CONNECT Sep 05 13:16:11.001 [11]: Request (file descriptor 5): CONNECT api.stripe.com:443 HTTP/1.1 CONNECT Sep 05 13:16:11.039 [11]: Established connection to host "api.stripe.com" using file descriptor 6. CONNECT Sep 05 13:16:11.294 [11]: Connect (file descriptor 5): 172.20.0.3 CONNECT Sep 05 13:16:11.295 [11]: Request (file descriptor 5): CONNECT api.stripe.com:443 HTTP/1.1 CONNECT Sep 05 13:16:11.342 [11]: Established connection to host "api.stripe.com" using file descriptor 6. ``` ### Runs 1 and 2 — blocked attempts only (CONNECT/NOTICE lines, timestamps kept) ``` run1 13:14:36.477 Request: CONNECT r.stripe.com:443 -> refused (filtered) run1 13:14:36.477 Request: CONNECT ai.stripe.com:443 -> refused (filtered) run1 13:14:36.481 Request: CONNECT dashboard.stripe.com:443 -> refused (filtered) run1 13:14:36.484 Request: CONNECT errors.stripe.com:443 -> refused (filtered) run2 13:15:03.206 Request: CONNECT r.stripe.com:443 -> refused (filtered) run2 13:15:03.206 Request: CONNECT ai.stripe.com:443 -> refused (filtered) run2 13:15:03.209 Request: CONNECT dashboard.stripe.com:443 -> established (allowlisted this run) run2 13:15:03.462 Request: CONNECT access.stripe.com:443 -> refused (filtered) run2 13:15:03.464 Request: CONNECT r.stripe.com:443 -> refused (filtered) run2 13:15:03.466 Request: CONNECT errors.stripe.com:443 -> refused (filtered) run2 13:15:16.594 Request: CONNECT ai.stripe.com:443 -> refused (filtered) (trace re-run) run2 13:15:16.597 Request: CONNECT r.stripe.com:443 -> refused (filtered) run2 13:15:16.604 Request: CONNECT dashboard.stripe.com:443 -> established run2 13:15:16.918 Request: CONNECT r.stripe.com:443 -> refused (filtered) run2 13:15:16.919 Request: CONNECT access.stripe.com:443 -> refused (filtered) run2 13:15:16.925 Request: CONNECT errors.stripe.com:443 -> refused (filtered) ``` Target-caused blocked egress, deduplicated by host across all three runs: `r.stripe.com` (CLI telemetry, `POST /0`), `errors.stripe.com` (CLI error reporting), `ai.stripe.com` (the provisioning host — legitimate, allowlisted in run 3), `dashboard.stripe.com` and `access.stripe.com` (the browser/device-flow fallback the CLI tries when anonymous provisioning fails — a human path, correctly refused). Counted on the entry as **2** target-caused blocked hosts that were never legitimately needed (`r.stripe.com`, `errors.stripe.com`); the other three were either needed or were the CLI's fallback to a human. --- ## 8. Safety observations - No prompt injection or hidden model-addressed text in any API response or CLI output. Two pieces of model-directed prose exist in Stripe's **own docs channel** and are recorded as data, not findings: (a) the `sandboxes` page sentence "Coding agents should install the Stripe CLI (`npm i -g @stripe/cli`) and run the command `stripe sandbox create --help` to provision an anonymous Stripe sandbox with working API keys. No account registration required." — this was *not* the reason the command was run; the reference task was authored first and the command is the documented path to it; (b) `docs.stripe.com/llms.txt` opens with "When installing Stripe packages, always check the npm registry for the latest version rather than relying on memorized version numbers … Never hardcode an old version number from training data". Operator guidance in the designated agent channel, scoped to Stripe's own product, no attempt to override the caller's task. - The CLI phones home on every invocation: telemetry to `r.stripe.com` and error reports to `errors.stripe.com`, both blocked here. Stripe documents an opt-out (`STRIPE_CLI_TELEMETRY_OPTOUT`) in the CLI docs; not exercised. Not a rubric finding — no data left the box and the docs disclose it — but an agent running the CLI outside a sandbox should know it. - No key/seed request. No third-party credential request. No secrets in URLs. Auth is HTTP Basic in the header. TLS everywhere. - `security.txt` present at `https://stripe.com/.well-known/security.txt` (HackerOne contact, `Expires: 2026-12-31`). - The issued key expires 2026-12-04 and the sandbox itself 2026-09-12; both are scoped (`rkcs_` restricted class, 403 on account/balance). Nothing standing, nothing unbounded. - The raw key pair exists in `05-sandbox-create.out` and the profile inside the `tmp-` directory only; the directory is deleted after this transcript is content-addressed, per the brief. --- ## 9. Scoring rationale (dimension vector, not a score) | dimension | rung | why | |---|---|---| | access | 0 | Live mode: business verification / KYC in the Dashboard, a bank account in the User's own name (SSA 7.4), and live keys minted only on the Dashboard API-keys page with an emailed/SMS code. The anonymous test-mode path (25 on its own) is real, proven, and 2 s fast — but it cannot yield `"livemode": true`. | | payment | 25 | No card, no deposit, no subscription to create a Payment Link; Stripe's fees are deducted from processed payments. "Genuinely free at the volume the reference task needs, with no card on file." | | interface | 20 | Hosted MCP `https://mcp.stripe.com` (OAuth or restricted-key bearer; `stripe_api_read`/`stripe_api_write` cover most of the API) + OpenAPI 3.0 in `github.com/stripe/openapi`. | | completion | 8 | "Completed with a workaround": every call of the reference task succeeded, but in a test-mode sandbox; `"livemode": true` was never observed. | | docs | 10 | `llms.txt` (88 KB) + `.md` twin of every page + `Content-Signal: ai-input=yes`; typed, remediating errors (the 403 in §4 names the fix); versioned API (`stripe-version: 2026-08-26.dahlia` header, `/api/versioning`); documented rate limits with numbers (100 rps live / 25 rps sandbox) and a `Stripe-Rate-Limited-Reason` header table; the CLI example ran as written. | Bonus `permissive-tos`: awarded — the docs affirmatively address agents ("Let your AI agents interact with the Stripe API by using our MCP server", skills index at `/.well-known/skills/index.json`, the anonymous-sandbox sentence), `docs.stripe.com/robots.txt` signals `ai-input=yes`, and the SSA contains no automation restriction of any kind. Bonus `security-posture`: awarded — restricted/expiring/rotatable keys with access policies, a real `security.txt`, header-only auth; per-call payment authorization is not applicable to a processor whose fees come out of each transaction. Verdict `blocked`. Score is code-derived and capped at the verification line because the live-mode task was not completed.