Security challenge — CryptoBook pivot
Role: adversarial security (not market, not legal except where a design is a credential crime waiting to happen).
Date: 18 September 2026.
Box we actually have: one public Ubuntu 24.04 VPS (vmi3003269, 173.249.19.205). App must bind 127.0.0.1, public only nginx 80/443. Postgres/Redis/Mongo on localhost. No HSM, no tenant KMS, no SOC2, SSH still a root-password-class login surface. We cannot honestly harden a vault of other people’s LLM keys, GitHub PATs, Slack tokens, or session cookies on this machine.
Ideas corpus: /root/cryptobook-next/ideas/ was empty after a 3-minute poll (06:24–06:27 UTC). Verdicts below are against BRIEF candidates A–E plus the naive implementations those one-liners always become. If product later pastes a BYOK proxy, a remote MCP that sees keys, or a “we log into X for you” agent, this document still kills it.
Kill tests (instant, non-negotiable):
- Users must hand LLM API keys (or cloud QPU keys, or GitHub/Slack/AWS tokens) to a random new SaaS, with no safer architecture.
- We proxy arbitrary third-party logins / hold session cookies (“browser agent in the cloud”).
- The product is a high-value credential honeypot we cannot harden on one VPS.
- We log secrets or prompt bodies by default.
Preferred pattern (the only pattern that survives on this VPS): local PEP sidecar / stdio MCP wrapper that never sees model keys; cloud is a policy compiler + hashed receipt ledger; prompt bodies off by default; no third-party login proxy.
Legend: KILL = do not build, even “encrypted at rest.” FIX = the wedge can live only if the architecture below replaces the naive one. SURVIVE = buildable now, but only with the architecture named in that section. There is no second architecture.
Threat model (this VPS, not a slide)
Assume attacker gets root or the Node app. That is in-scope; it is how VPS SaaS dies.
| If we store… | Blast radius on this box | Verdict |
|---|---|---|
| Customer OpenAI/Anthropic/Google keys | Attacker drains every org bill, exfiltrates fine-tunes, impersonates the customer to the model provider | Uninsurable honeypot. KILL |
| GitHub PATs / Slack / AWS / “tool” secrets | Source, prod, chat, cloud. One RCE = industry news | KILL |
| Browser cookies / “login as the user” | CFAA-adjacent, account takeover factory | KILL |
| Prompt bodies / tool args by default | Trade secrets, passwords pasted into agents, customer PII | KILL default; opt-in local-only only |
| Our own scoped project tokens + Ed25519 pubkeys + policy text + receipt hashes | Integrity nuisance, metadata leak, not a key vault | Acceptable |
| Stripe customer IDs (no PAN) + emails | Normal SaaS, still minimize | Acceptable |
“We encrypt keys with a server-side KEK in env” is security theater on a single VPS: the app must decrypt to proxy, so memory, swap, core dumps, DEBUG logs, and the stolen .env all have the KEK. Do not ship that.
nginx access logs, PM2 logs, and “just while we debug” request dumps will store Authorization and prompt JSON unless the product refuses to receive them. Architecture, not a logging config, is the control.
A. Permit gateway for agent tools/MCP — “one URL drop-in”
Naive product (what “one URL drop-in” means in 2026): hosted Streamable-HTTP MCP or reverse proxy. User pastes https://cryptobook.space/p/… into Cursor/Claude. We see every tool call. Spend cap on “the user’s own keys” means we terminate TLS to OpenAI with their key. This is Helicone/LiteLLM with a new coat of paint.
Verdict: KILL the hosted drop-in proxy.
Verdict: FIX the wedge (English rules, allow/deny/ask, spend cap, signed receipts) into the architecture in §Canonical.
Why the proxy dies:
- It is a key honeypot by feature design. The URL is the product; the URL is also where keys and tool args go.
- Spend caps require seeing billed requests if we are the HTTP client. Then we have the key.
- “Ask” mode on a hosted gateway means pending tool payloads sit in our Redis. Those payloads are secrets.
- We are not Okta. A new
.spaceSaaS asking for org-widesk-…will either get zero serious users or the worst users (keys they can afford to burn). Neither is a business; the second is a liability. - Compromise of this VPS becomes “CryptoBook stole 4,000 OpenAI keys.” That is not a bug bounty; that is the company.
Not an acceptable FIX: envelope encryption, “keys in the browser,” “we are just a proxy, we don’t store them” (they are in RAM on every call), customer-managed keys that we still use as the TLS client.
Acceptable FIX: do not terminate model or tool credentials. Cloud = policy compiler + PDP that receives non-secret decision requests (tool name, arg hash, estimated USD, policy id). PEP = local sidecar. See §Canonical.
B. Repo agent-permit.yaml runtime — Dependabot-like, “hosted enforcement”
Naive product: GitHub App plus our servers run or proxy the agent in CI with OPENAI_API_KEY and GITHUB_TOKEN injected into our runtime.
Verdict: KILL hosted enforcement that executes agents or holds CI secrets.
Verdict: FIX to “Dependabot for the policy file” + local/CI PEP.
Allowed:
- GitHub App with least-privilege installation: read
agent-permit.yaml, write check runs / PR comments. That token is GitHub’s installation token for our app, not a customer PAT we store, and not an LLM key. - CI action that is open source, runs on the customer’s runner, reads repo secrets in their job, enforces policy, uploads hashed receipts to us.
Forbidden:
- “Enable CryptoBook hosted runners, paste org secrets.”
- Webhook that forwards workflow logs (secrets leak in logs constantly).
- Storing
GITHUB_TOKEN/ Actions secrets “to re-run the agent for a receipt.”
Viral YAML in the repo is fine. Enforcement affinity is the customer’s process, not ours.
C. Freelance / client agent receipts — “prove the agent did the work”
Naive product: full transcript + tool I/O hosted so the client can “see what the bot did.” That is a confidential-work dump on a box we cannot isolate per tenant. Freelancers will paste API keys, customer data, and credentials into prompts. Default logging = we become their lawyer’s discovery archive and the attacker’s corpus.
Verdict: FIX (hashed / user-held preimage receipts). Not KILL if we never host plaintext work product.
Only acceptable receipt:
- Sidecar hashes each event (
sha256(canonical_event)), builds a Merkle tree, signs the root with a locally generated Ed25519 key. - Cloud stores:
{receipt_id, pubkey, policy_hash, merkle_root, sig, event_count, spend_estimate, created_at}. - Share URL shows hashes and signature verification, not bodies.
- If a freelancer wants the client to read prose: they send the export out of band (or a client-side encrypted blob whose key never touches our server). We do not “helpfully” store the plaintext for a nicer viewer.
- Prompt bodies off. Tool args hashed. No replay debugger in the cloud.
Do not build “session replay for agents.” That is observability-as-honeypot.
D. Quantum circuit classroom
Verdict: SURVIVE (security only), with a hard fence.
This is not a credential product if it is a browser/WASM or localhost simulator. No IBM/AWS/IonQ/Azure Quantum keys. No “we’ll run this on real hardware, paste your IBM token.” That token is as hot as an LLM key and we have no QPU; collecting it is both a honeypot and a lie.
Fence:
- Simulator and pedagogy only. Disclose it is not a QPU.
- Account = email + password hash / OAuth to us, nothing else.
- Do not add “bring your Braket key” in v2. That flips this to KILL.
(Demand/monetization may still kill this elsewhere. Security does not.)
E. “Anything more specific” (none filed)
Verdict: KILL the unstated cluster that idea agents always reach for in this market:
| Unstated idea | Why it dies |
|---|---|
| Hosted BYOK LLM gateway / “OpenAI proxy with budgets” | Key honeypot; me-too Helicone |
| Cloud Computer-Use / “agent logs into Notion/GitHub/bank” | Third-party login proxy + cookie vault + CFAA heat |
| Agent observability that stores prompts (Langfuse clone) | Default secret log; crowded; we cannot isolate tenant traces |
| “Agent identity provider” holding long-lived tool OAuth refresh tokens | Okta-shaped honeypot we cannot harden |
| Quantum job broker (submit circuits with customer QPU keys) | Key custody + hardware we do not have |
Canonical product that patches the kills — SURVIVE
Name the thing honestly: a policy compiler + local permit sidecar (PEP) + hashed receipt ledger. Not a proxy. Not a key wallet. Not an observability lake.
This is BRIEF A+B+C with the poison cut out. This is the only security architecture allowed if the company builds in the agent-permit space.
Split
| Piece | Where it runs | What it is allowed to see |
|---|---|---|
| Policy compiler | Our VPS | English rules → signed policy bundle. No keys, no prompts. |
| PDP (optional, thin) | Our VPS | tool_name, args_hash, usd_estimate, policy_id, sidecar auth. Answer: allow / deny / ask. |
| PEP sidecar / stdio MCP wrapper | Customer machine or their CI runner | Tool calls, local spend counters, local LLM HTTP if they enable a local spend wrap. Never forwards model keys. |
| Receipt ledger | Our VPS | Merkle roots, signatures, policy hashes, aggregates. |
| Playground | Our VPS + browser | Sample traces, fake tools (calc, weather). Zero user secrets for the 60-second path. |
Rules that are product law, not a backlog ticket
- LLM API keys never leave the user’s runtime. We have no column, no header, no “paste your OpenAI key to try spend caps.” Spend caps: local wrapper around the provider HTTP client on their box, or provider usage APIs called from the sidecar. Cloud gets signed
{tokens, usd_estimate, model}, not the key and not the prompt. - MCP wrapper does not need the model key (the host already has it). Wrap tools, not the chat API, unless the spend wrap is local.
- No remote MCP as the default path if that MCP would see real tools or real credentials. Demo remote MCP may only expose toy tools we own with no customer auth.
- No third-party login proxy. We may use GitHub OAuth as login to CryptoBook. We may ship a GitHub App for our checks. We do not OAuth-on-behalf into the customer’s Slack/Google/GitHub as an agent. Sidecar talks to local MCP servers that already hold those tokens.
- Prompt bodies and raw tool arguments are not stored, not uploaded, not in nginx logs. Default is hash-only. An
--upload-bodiesflag is a footgun: do not implement it in v1. If ever, it is local disk, not our API. - Ask/approve is local. Pending payloads stay in the sidecar. Cloud may notify “tool
repo.force_pushwants approval” and record the signed decision. If the only pretty UI is our website, the payload would have to come to us — that UI is forbidden for real payloads. Local TUI / desktop notification /permit approveCLI. - Receipt integrity: sidecar generates Ed25519 at init; user registers pubkey with the project. Stolen project token can post garbage but cannot forge a valid receipt. Verify on a public page with the pubkey. We log hashes, not preimages.
- Project tokens are ours, scoped to
fetch_policy+post_receipt, rotatable, hashed at rest (store only a hash, like a password). They are not customer crown jewels, but treat them as credentials anyway. - Do not log secrets by default: structured logs with explicit allowlists. Redact
authorization,cookie,api_key,token,sk-,Bearer. Refuse request bodies on any public route that could be a mistaken proxy. - Bind
127.0.0.1. No extra ports. Do not reuse LiveKit/TURN for this product. - Honest copy: “We never take your model keys. Enforcement runs next to your agent. We store hashes, not chats.” If we cannot say that on the homepage, the architecture has already drifted to KILL.
60-second path that does not take keys
- Homepage: paste three English rules → compiled
agent-permit.yaml+ allow/deny on canned tool traces in the browser (WASM/JS evaluator). npxlocal sidecar, point Claude/Cursor at stdio MCP.- Optional account to publish a policy and later verify a receipt. Account is not required to get the first deny.
That is easy start without becoming a key dropbox.
What a VPS breach then means (acceptable)
Attacker gets emails, policy text (internal tool names — disclose this), receipt hashes, Stripe customer IDs, hashed project tokens. They do not get OpenAI keys, GitHub PATs, prompts, or the ability to mint valid receipts for a sidecar they do not have the Ed25519 secret for. That is the difference between a dead company and a bad week.
Scoreboard
| ID | Idea | Verdict | Only acceptable architecture |
|---|---|---|---|
| A | Hosted permit gateway / one-URL MCP or LLM proxy | KILL | — |
| A′ | English permit rules + spend cap + receipts, local PEP | FIX → SURVIVE | §Canonical: sidecar/MCP wrapper never sees model keys; cloud = compiler + hash ledger |
| B | agent-permit.yaml hosted agent runtime |
KILL | — |
| B′ | YAML in repo + GitHub checks + customer-runner PEP | FIX → SURVIVE | App installation token only; secrets stay in their CI; hashed receipts |
| C | Freelance receipts with hosted transcripts | KILL default logging | — |
| C′ | Shareable hashed receipts | SURVIVE | Merkle + local Ed25519; no bodies on our disk |
| D | Quantum classroom (simulator) | SURVIVE | No QPU keys ever |
| D′ | Quantum classroom + “paste IBM/Braket key” | KILL | — |
| E | BYOK proxy, cloud browser-login agent, prompt lake, agent IdP vault | KILL | — |
| F | Canonical Permit (compiler + local sidecar + hash ledger) | SURVIVE | §Canonical only |
Ship F. Anything that needs the user’s sk- on 173.249.19.205 is a honeypot with a landing page.