# Probe transcript — Porkbun (e2e, sandbox variant of the reference task) | | | |---|---| | Entry | `entries/porkbun.yaml` | | Target | https://porkbun.com · API `https://api.porkbun.com/api/json/v3` | | Mode | `e2e` after `gate` | | Probe id | `porkbun-e2e` | | Rubric | `rubric/v0.2.json` sha256 `cd605a44799f49951bf078fda63c5b21ad3c07db494f41d38ad19ce729c5551a` | | Date | 2026-09-05 | | Sandbox up | 2026-09-05T13:12:18Z (proxy init, from the egress log) | | Sandbox down | 2026-09-05T13:13:45Z | | Wall clock | 87 s (epoch 1788613938 → 1788614025, measured from the shell invocation that brought the sandbox up to completion of teardown) | | Real money spent | $0.00 — every registration call ran against a Porkbun **sandbox** account seeded with $1,000 of fake credit; no payment instrument was presented and none was requested | | Human interventions | 0 | | Verdict | `blocked` — the sandbox variant completed end-to-end; the **production** reference task is walled by human-only preconditions that this run did not (and could not) satisfy. See §7 | | Secrets carried in | none. `PROBE_WALLET_KEY` unset, `PROBE_INBOX` unset, `PROBE_SPEND_CAP_USD=0`. The only credential in the run was a throwaway sandbox key pair minted *by the target* during the run; it is 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. ```sh ./.claude/skills/probe/probe-sandbox.sh up --id porkbun-e2e --target api.porkbun.com ``` Allowlist: `api.porkbun.com`. Nothing else. (`porkbun.com` — the human web app and the `/account/*` pages — was deliberately **not** allowlisted; its `robots.txt` disallows `/account/*` and nothing in this run needed it.) User-Agent on every request: `ag3ntsearch-probe/0.2 (+https://github.com/ag3nt/ag3nt-search)`. --- ## 0. Reference task The reference task recorded on the entry (production wording): > Starting from nothing, obtain Porkbun API credentials and register a new `.com` > domain via `POST https://api.porkbun.com/api/json/v3/domain/create/{domain}`, > passing only if a subsequent `POST /domain/get/{domain}` returns `status: > "SUCCESS"` with the domain `ACTIVE` and `"sandbox": false`. This run executes the **sandbox variant** — identical calls, identical base URL, but authenticated with a `pk1_sb_`/`sk1_sb_` key pair that Porkbun's own docs describe as "throwaway test account, $1000 fake credit — no account, no approval". The variant proves the entire API path (credential mint → check → dry run → create → verify) with zero human and zero money. It cannot prove the last clause of the production task (`"sandbox": false`), because production registration requires, per the target's own docs (§7), a verified email **and phone**, at least one prior human-placed registration, and real account credit. That is the wall, and it is recorded as the verdict. --- ## 1. Control: unauthenticated pricing read — PASS The docs claim `GET /pricing/get` needs no auth. Verified first, as a control that the sandbox's egress path worked before any credential existed. ``` $ curl -sS "https://api.porkbun.com/api/json/v3/pricing/get" step0 http=200 time=9.372947s size=82223 status SUCCESS tlds 907 com {"registration":"11.08","renewal":"11.08","transfer":"11.08","coupons":[]} ``` 907 TLDs priced; `.com` registration $11.08. The 9.4 s latency was this one call only (a cold 82 KB payload); every subsequent call was 0.7–2.6 s. --- ## 2. Keyless credential mint — PASS (the headline result) `POST /apikey/request` with `{"sandbox": true}`. No account, no cookie, no prior credential of any kind. ``` $ curl -sS -H 'Content-Type: application/json' \ -d '{"sandbox":true,"name":"ag3ntsearch-probe"}' \ https://api.porkbun.com/api/json/v3/apikey/request step1 http=200 time=1.991172s size=555 ``` Response headers (verbatim, the target's anonymous `BUNSESSION2` session cookie value is redacted as hygiene; API auth is key-based and never used it): ``` HTTP/2 200 date: Sat, 05 Sep 2026 13:12:42 GMT content-type: application/json server: openresty set-cookie: BUNSESSION2=[redacted]; path=/; secure; HttpOnly; SameSite=Lax cache-control: no-store, no-cache, must-revalidate access-control-allow-origin: * access-control-allow-headers: Content-Type, Authorization, X-API-Key, X-Secret-API-Key, Sig x-ratelimit-limit: 20 x-ratelimit-remaining: 19 x-ratelimit-reset: 1788617560 x-request-id: 01a071b3-24b8-7c47-a421-57b48a023ac9 x-api-version: 3.18 link: ; rel="describedby" strict-transport-security: max-age=63072000; includeSubDomains; preload ``` Body (keys redacted to their prefix): ```json { "status": "SUCCESS", "apikey": "pk1_sb_4…[redacted]", "secretapikey": "sk1_sb_c…[redacted]", "sandbox": true, "message": "Sandbox key issued (throwaway test account with $1000 fake credit). Use it as apikey/secretapikey against the same base URL — every call runs in the isolated sandbox (no real registry actions, DNS changes, or charges). Top up or reset with /sandbox/topup and /sandbox/reset.", "requestId": "01a071b3-24b8-7c47-a421-57b48a023ac9" } ``` Observations: - The documented rate limit for this endpoint ("20 requests per IP per 3600 seconds") is enforced and **signalled** in headers (`x-ratelimit-limit: 20`, `remaining: 19`). One request was made; none more. - Every response carries `link: <…/spec>; rel="describedby"` — the API self-describes its OpenAPI document on the wire, exactly as `llms.txt` says. - `x-api-version: 3.18` matches `info.version` in the spec. - The `message` field is prose addressed to the caller. It is documentation, not an instruction to a model; it was read as data. Nothing in it was acted on except the documented fact that the keys work against the same base URL. --- ## 3. Credential check — PASS ``` $ POST /ping (X-API-Key / X-Secret-API-Key headers) 02-ping http=200 time=2.031170s size=169 x-porkbun-sandbox: true {"status":"SUCCESS","yourIp":"152.117.90.101","xForwardedFor":"152.117.90.101","credentialsValid":true,"requestId":"01a071b3-87c8-7dab-bf98-985c01b11afc","sandbox":true} ``` Header auth (`X-API-Key`/`X-Secret-API-Key`) works as documented; no secret in any URL. The `yourIp` is the egress proxy's public address, not anything about the host. --- ## 4. Availability + price — PASS Domain chosen: `ag3ntsearch-probe-1788613984.com` (epoch-suffixed so a re-probe never collides). ``` $ POST /domain/checkDomain/ag3ntsearch-probe-1788613984.com 03-check http=200 time=0.892837s size=499 {"status":"SUCCESS","response":{"avail":"yes","type":"registration","price":"11.08","firstYearPromo":"no","regularPrice":"11.08","premium":"no","additional":{"renewal":{"type":"renewal","price":"11.08","regularPrice":"11.08"},"transfer":{"type":"transfer","price":"11.08","regularPrice":"11.08"}},"minDuration":1},"limits":{"TTL":10,"limit":1,"used":1,"naturalLanguage":"1 out of 1 checks within 10 seconds used."},"ttlRemaining":10,"requestId":"01a071b3-8ad1-7c38-bc44-41da53f214e4","sandbox":true} ``` Note the per-endpoint quota is returned **in the body** with a natural-language rendering ("1 out of 1 checks within 10 seconds used") in addition to the machine fields — a small but real agent-legibility feature. Docs mismatch, minor: the spec says `cost` on `/domain/create` is "in pennies (USD cents)", while `checkDomain` returns `"price": "11.08"` as a dollar string. The conversion (× 100 → `1108`) had to be inferred; the dry run in §5 confirmed it. Recorded as a legibility wrinkle, not a failure. --- ## 5. Dry run — PASS ``` $ POST /domain/create/ag3ntsearch-probe-1788613984.com {"cost":1108,"agreeToTerms":"yes","dryRun":true} 04-dryrun http=200 time=2.602591s size=446 {"status":"SUCCESS","dryRun":true,"wouldSucceed":true,"operation":"registration","domain":"ag3ntsearch-probe-1788613984.com","tld":"com","available":"available","premium":false,"duration":1,"cost":1108,"costDisplay":"$11.08","balance":100000,"sufficientFunds":true,"message":"Dry run: this registration would succeed and cost $11.08. No order was created and no charge was made.","requestId":"01a071b3-8e48-7dd6-8de6-afab82685955","sandbox":true} ``` `balance: 100000` (cents) = the documented $1,000 fake credit. `wouldSucceed: true` — so the production preconditions ("email and phone verified", "at least one previous registration") are **waived inside the sandbox account**; the sandbox does not exercise those walls. --- ## 6. Registration + idempotent replay + verification — PASS (sandbox) ``` $ POST /domain/create/… {"cost":1108,"agreeToTerms":"yes"} -H 'Idempotency-Key: ag3ntsearch-1788613990' 05-create http=200 time=1.989766s size=440 x-request-id: 01a071b3-9886-7109-bdf7-098dace0c0d6 x-porkbun-sandbox: true {"status":"SUCCESS","domain":"ag3ntsearch-probe-1788613984.com","cost":1108,"orderId":9912992,"limits":{"attempts":{"TTL":1,"limit":1,"used":1,…},"success":{"TTL":86400,"limit":50,"used":1,"naturalLanguage":"1 out of 50 successful creates within 86400 seconds used."}},"balance":98892,"ttlRemaining":86399,"requestId":"01a071b3-9886-7109-bdf7-098dace0c0d6","sandbox":true} $ (same request, same Idempotency-Key) 05b-create-replay http=200 time=0.677149s size=440 idempotent-replayed: true x-request-id: 01a071b3-9886-7109-bdf7-098dace0c0d6 (body byte-identical to 05-create; balance still 98892 — not charged twice) $ POST /domain/get/ag3ntsearch-probe-1788613984.com 06-get http=200 time=0.666409s size=322 {"status":"SUCCESS","domain":{"domain":"ag3ntsearch-probe-1788613984.com","status":"ACTIVE","tld":"com","createDate":"2026-09-05 13:13:11","expireDate":"2027-09-05 13:13:11","securityLock":0,"whoisPrivacy":"1","autoRenew":"1","apiAccess":"1","notLocal":0},"requestId":"01a071b3-a327-7519-8a6a-7ea20fc94ebc","sandbox":true} $ POST /domain/listAll 07-listAll http=200 time=1.649893s size=335 {"status":"SUCCESS","count":1,"domains":[{"domain":"ag3ntsearch-probe-1788613984.com","status":"ACTIVE",…}],…,"sandbox":true} ``` Machine check: ``` check: get.status=SUCCESS inListAll=true sandboxFlag=true REFERENCE_TASK_PASS (sandbox) ``` Balance went 100000 → 98892 = exactly the quoted 1108. The replay returned the **same** `x-request-id` and `orderId`, with `idempotent-replayed: true`, and did not debit again. One wrinkle: the replayed response omits the `x-porkbun-sandbox: true` header that every other sandbox response carried (the body still says `"sandbox": true`). The docs say "Every sandbox response includes … an `X-Porkbun-Sandbox: true` header" — the cached replay is the exception. Minor. `"autoRenew": "1"` by default — the registration agreement's auto-renewal is on unless disabled (`/domain/updateAutoRenew`). An agent registering for real should know that before it commits funds. --- ## 7. The wall (why the verdict is `blocked`, not `completed`) Nothing in this run touched it, because the sandbox waives it — but it is documented unambiguously by the target, and it decides the production task: From `https://porkbun.com/llms/domain` (retrieved 2026-09-05), prerequisites for `POST /domain/create/{domain}`: ```untrusted-content Account email and phone must be verified Account must have sufficient credit Account must have placed at least one previous domain registration Premium domains cannot be registered via API ``` From `https://porkbun.com/llms/guides/register-a-domain`: ```untrusted-content Your account email and phone must be verified before the first registration ``` From the OpenAPI spec, `/apikey/request` (the non-sandbox path): ```untrusted-content Returns a `requestToken` and `authUrl` that the account holder must visit while logged into Porkbun to approve the request. The `authUrl` remains valid for 30 minutes (accommodating new-account creation and email verification). ``` So the production chain is: a human creates the account (the `/account/*` pages are `robots.txt`-disallowed and were not fetched), verifies email **and phone**, places one registration themselves, funds the account (card / PayPal / "USDC cryptocurrency payments via Coinbase Business" through the web checkout — there is no funding endpoint in the spec), and approves the agent's `authUrl`. After that the agent is fully autonomous, with the API path this run proved. Rubric placement: phone verification → `access: 0`; blocker `kind: sms`. --- ## 8. Egress log (verbatim, proxy sidecar) ``` DEBUG Sep 05 13:12:18.495 [11]: sending stored logs NOTICE Sep 05 13:12:18.496 [11]: Initializing tinyproxy ... NOTICE Sep 05 13:12:18.497 [11]: Reloading config file (/etc/tinyproxy/tinyproxy.conf) WARNING Sep 05 13:12:18.498 [11]: You SHOULD set a UserName in the config file. Using current user instead. NOTICE Sep 05 13:12:18.498 [11]: Reloading config file finished DEBUG Sep 05 13:12:18.498 [11]: done sending stored logs CONNECT Sep 05 13:12:31.191 [11]: Connect (file descriptor 5): 172.22.0.3 CONNECT Sep 05 13:12:31.192 [11]: Request (file descriptor 5): CONNECT api.porkbun.com:443 HTTP/1.1 CONNECT Sep 05 13:12:31.280 [11]: Established connection to host "api.porkbun.com" using file descriptor 6. CONNECT Sep 05 13:12:40.619 [11]: Connect (file descriptor 5): 172.22.0.3 CONNECT Sep 05 13:12:40.621 [11]: Request (file descriptor 5): CONNECT api.porkbun.com:443 HTTP/1.1 CONNECT Sep 05 13:12:40.688 [11]: Established connection to host "api.porkbun.com" using file descriptor 6. CONNECT Sep 05 13:13:04.591 [11]: Connect (file descriptor 5): 172.22.0.3 CONNECT Sep 05 13:13:04.593 [11]: Request (file descriptor 5): CONNECT api.porkbun.com:443 HTTP/1.1 CONNECT Sep 05 13:13:04.699 [11]: Established connection to host "api.porkbun.com" using file descriptor 6. CONNECT Sep 05 13:13:06.638 [11]: Connect (file descriptor 5): 172.22.0.3 CONNECT Sep 05 13:13:06.640 [11]: Request (file descriptor 5): CONNECT api.porkbun.com:443 HTTP/1.1 CONNECT Sep 05 13:13:06.774 [11]: Established connection to host "api.porkbun.com" using file descriptor 6. CONNECT Sep 05 13:13:07.585 [11]: Connect (file descriptor 5): 172.22.0.3 CONNECT Sep 05 13:13:07.586 [11]: Request (file descriptor 5): CONNECT api.porkbun.com:443 HTTP/1.1 CONNECT Sep 05 13:13:07.653 [11]: Established connection to host "api.porkbun.com" using file descriptor 6. CONNECT Sep 05 13:13:10.206 [11]: Connect (file descriptor 5): 172.22.0.3 CONNECT Sep 05 13:13:10.208 [11]: Request (file descriptor 5): CONNECT api.porkbun.com:443 HTTP/1.1 CONNECT Sep 05 13:13:10.279 [11]: Established connection to host "api.porkbun.com" using file descriptor 6. CONNECT Sep 05 13:13:12.207 [11]: Connect (file descriptor 5): 172.22.0.3 CONNECT Sep 05 13:13:12.209 [11]: Request (file descriptor 5): CONNECT api.porkbun.com:443 HTTP/1.1 CONNECT Sep 05 13:13:12.274 [11]: Established connection to host "api.porkbun.com" using file descriptor 6. CONNECT Sep 05 13:13:12.899 [11]: Connect (file descriptor 5): 172.22.0.3 CONNECT Sep 05 13:13:12.904 [11]: Request (file descriptor 5): CONNECT api.porkbun.com:443 HTTP/1.1 CONNECT Sep 05 13:13:13.005 [11]: Established connection to host "api.porkbun.com" using file descriptor 6. CONNECT Sep 05 13:13:13.580 [11]: Connect (file descriptor 5): 172.22.0.3 CONNECT Sep 05 13:13:13.583 [11]: Request (file descriptor 5): CONNECT api.porkbun.com:443 HTTP/1.1 CONNECT Sep 05 13:13:14.654 [11]: Established connection to host "api.porkbun.com" using file descriptor 6. ``` Nine CONNECTs, all to `api.porkbun.com`. **Zero blocked attempts.** The target never tried to redirect the client off-domain; no CDN, telemetry, or third-party host was contacted. --- ## 9. Safety observations - No prompt injection, hidden text, or model-addressed imperative in any response or in any public page fetched during gate mode (`/api/json/v3/documentation`, `/llms.txt`, `/llms/domain`, `/llms/guides/*`, `/mcp`, `/api/json/v3/spec`, `/legal/agreement/domain_name_registration_agreement`, `/robots.txt`, `/support/payment_options`, KB article 143). The `message` strings are caller-facing docs prose. - No key/seed request. No third-party credential request. No secrets in URLs (header or body auth only). TLS everywhere, HSTS preload. - No `security.txt` at `https://porkbun.com/.well-known/security.txt` (HTTP 404) and none at `api.porkbun.com` (also 404 for `robots.txt`). Recorded on the entry; it is the reason `security-posture` was withheld. - Rate limits honoured: 1 of 20 `/apikey/request`, 1 of 1 `checkDomain` per 10 s, 1 of 50 creates per day. Nothing was probed for its ceiling. - The sandbox key pair exists in the raw collected artifact `01-apikey-request.json` inside the `tmp-` directory only; the directory is deleted after this transcript is content-addressed, per the brief. --- ## 10. Scoring rationale (dimension vector, not a score) | dimension | rung | why | |---|---|---| | access | 0 | Production registration needs a verified **phone** ("Account email and phone must be verified"), a human-placed first registration, and a human to create the account and approve the `authUrl`. The keyless sandbox mint (25 on its own) is real and proven, but it cannot register a real domain. | | payment | 20 | Account credit, prepaid; fundable with "USDC cryptocurrency payments via Coinbase Business" — but only through the web checkout; no funding endpoint exists in the API. "Prepaid credits purchasable with crypto" rung. | | interface | 20 | Official MCP server (`npx -y @porkbunllc/mcp-server`, 81 tools, read/destructive annotations) + OpenAPI 3.0 `Porkbun API v3` `3.18` self-advertised on every response via `Link: rel="describedby"`. | | completion | 8 | "Partially completed, or completed with a workaround": every call of the reference task succeeded, but in a sandbox account that waives the production walls; `"sandbox": false` was never observed. | | docs | 10 | `llms.txt` + `llms-full.txt` + per-topic `/llms/*` guides written for agents; machine-readable errors; rate limits both documented per endpoint and signalled in headers; `x-api-version` on the wire; the documented sandbox flow ran **exactly** as written on the first attempt. The one legibility wrinkle (dollar-string price vs cents `cost`) did not cost a rung. | Bonus `permissive-tos`: awarded — "The domain API built for developers & AI agents", "built for AI agents and automation" (`llms.txt`), an official MCP server, and an OpenAPI spec advertised on-wire. Bonus `security-posture`: withheld — per-key IP/domain restrictions, spend caps, `dryRun`, and cost-must- match-quote are genuinely strong, but there is no `security.txt` anywhere and the rubric condition is a conjunction. Verdict `blocked`. Score is code-derived and capped at the verification line because the production task was not completed.