Command OS · adversarial review
20px

Codex adversarial review · 2026-08-25

Nine findings. Three critical. One of them is live right now.

I asked Codex to break the plan rather than validate it, and it did. Two of my own claims were wrong, the entity model could not answer the question the PRD uses to justify its own layer, and the review found a shipping defect that has nothing to do with v2 at all. The PRD has been updated. This is what changed and why.

Findings 9 Critical 3 My errors 2 Plan changes 6
01

The one that isn't about v2

Handed to the ghl-command-mcp lane on your instruction. Not being worked here.

F2Critical · live in the field

The cockpit accepts unauthenticated cross-origin writes

Thirteen POST routes, no auth, no CSRF token, no Origin check, no Host check, and JSON.parse on whatever arrives. I verified this in the source before relaying it.

Binding to 127.0.0.1 stops remote attackers. It does not stop a web page the operator is already looking at: a body with no explicit content type is a CORS "simple request", so no preflight fires and the write lands. The worst route needs no secret at all — /api/new-client takes a name and provisions a real GHL sub-account with the agency key.

Handed off168-line packet with file:line evidence, the exploit chain, the full route inventory split by blind-exploitable versus needs-an-ID, the four-step fix, and the regression tests. Roughly a day of work. Step 1 alone — reject non-JSON bodies — closes the blind path outright.

02

Your sub-account question

Killed on a structural fact, before any liability argument gets started.

F1Critical

GHL has no tenant boundary below the agency

Sub-account creation is an agency-level permission, and GHL has exactly two user types: Agency, which sees every sub-account in the company, and Account, which is restricted and cannot create. There is no third setting.

So reselling gives you two options and both end badly. Grant Agency access and every customer can see every other customer's client list. Keep it to yourself and every new client any customer signs is a manual ticket to you, forever — fifty customers signing one client a month is fifty tickets gating their revenue, on your calendar.

Exit is worse: white-labelled sub-accounts cannot be transferred at all. Both sides need active agency accounts, both must consent by support ticket, phone numbers stay behind. A snapshot is not a fallback — it carries structure, not contacts, conversations or history.

And the money never justified it. GHL Unlimited is $297/mo for unlimited sub-accounts; affiliate pays 40% recurring, so $118.80/mo forever at zero cost. Reselling at $49 only wins for customers with five or fewer clients — exactly the people who cannot afford $497 to $997/mo. Muhammad needs it because his price is $197.

Recorded as D14, awaiting youGuided provisioning into their own agency account via our affiliate link, plus credit their first month of GHL ($297) against their Command OS invoice. Costs $297 once, pays back in about 2.5 months, and keeps D2, D10 and D11 all true. Optional add: a hard-capped, synthetic-data-only, auto-expiring demo sub-account, enforced in code the way our protected-locations refusal already is.

One check before we promise it

GHL markets automated sub-account creation as a SaaS Pro ($497) feature. If an agency token with locations.write cannot create sub-accounts on Unlimited ($297), the guided-provisioning economics shift. I can test this against a real account whenever you want — it is one call.

03

What it broke in my plan

All of these are now fixed in the PRD. Two of them were my errors, stated to you as fact.

F5Highmy error

The nine entities cannot answer the question the PRD uses to justify Layer 2

The flagship demo is "which clients are paying us but have had no appointments booked in three weeks?" That joins Stripe to GHL. GHL's tenant boundary is locationId; Stripe has no tenant boundary at all. Nothing in my nine entities carried "which of my agency's clients is this?" The model could not express its own headline example.

Worse on connector three: a GHL appointment and its synced Google Calendar event both map to Appointment. The brief says fourteen when there are seven. Layer 3 is a trust product and it gets one chance.

ChangedA thin shared spine of Tenant, Identity + Link (confidence-scored and reversible) and an append-only observation log carrying source and observed-at per field — plus per-connector native records stored verbatim, with cross-system views projected on read. The canonical shape stops being a lossy write-time decision and becomes something we can rewrite in an afternoon.

F4Highmy error

"1,025 tests make the refactor safe" was wrong

45 of 59 tool modules have no test file — including location-switcher at 700 lines and 11 tools, plus workflow-builder, funnel-builder and intake-to-build. The suite is dense where the refactor is easy and absent where it is hard.

The repo already documented this failure mode: a tool-count constant disagreed with the code for twelve releases while the suite agreed with itself the whole time, because the same constant fed both the claim and the check on it.

ChangedInvert the layering. The connector runtime consumes the 242 tools rather than sitting beneath them, so "no user-visible change" is true by construction instead of by hope. And build the second connector against the runtime first, with GHL untouched — the old order committed the abstraction with a sample size of one.

F6High

"Every line verified" is already false in our own code

There are GHL objects we can create and cannot reliably read back: workflow internals, users (the IAM route returns 401), A2P campaign status, phone provisioning, DKIM, calendar OAuth grants, bot config, snapshot-imported IDs. Our own handoff code already types sections as "or unavailable" for exactly this reason, with a comment saying an unreadable users list must never render as "no users".

ChangedThree states, not two: VERIFIED (read back, value shown), ASSERTED (written, success returned, could not read back, here is why), PENDING-THIRD-PARTY (A2P, DNS, OAuth — with the named check that resolves it). The document refuses to print a "done" heading while any line is ASSERTED. Stricter than my claim, and unlike it, achievable.

F7High

The browser route is not a browser

It is an undocumented internal backend driven by a refresh token that authenticates the human, agency-wide — not scoped to workflows, not scoped to a location. Anything the owner can do in the GHL UI, that token can do.

And "degrade to a manual task" doesn't fail one step, it falsifies a tier. Tier 1 sells as "one day, done right", but an if/else branch needs that route. GHL adds a bot check on a Tuesday and every Tier 1 build that day becomes fourteen manual steps handed to a VA who by our own pitch doesn't know GHL.

ChangedRoutes are now a first-class field in the module contract, alongside connectors and writes, with a declared degraded behaviour. Tier 1 contains nothing requiring that route. Anything only reachable through it is Tier 2+ and sold with an explicit human-step clause. Plus a daily route canary, so we find out before a customer does.

F3Critical

The approve gate is settable from inside the client's own GHL account

We mirror board state into a custom value in the client's sub-account, merged last-write-wins on an unauthenticated timestamp. So the client, their staff, any VA with sub-account access, or any GHL workflow containing a "set custom value" action can mark the human approve gate done — which unblocks the staged build, which writes live.

Handed off with F2One line of policy: gates never sync, only progress does. Then sign the shared record so the "who wrote this" field means something. Also worth revisiting whether a customer-visible custom value is the right transport at all — it leaks our delivery status to the end client and persists after churn.

04

Missing entirely

Not weaknesses in the plan. Absences from it. All now recorded in PRD §13 so they don't get rediscovered in six months.

GapWhy it bitesWhose
No rollback or change journalThe build captures no before-images. There is no answer to "what did the OS change in this account on Tuesday", and no undo the first time a VA builds into the wrong client. Our own mutation ritual requires this and the product doesn't implement it.MCP lane
No Claude CLI version floorStages depend on the model emitting a literal RESULT: line. A CLI or model change breaks every stage at once, silently, for every customer. At $997/mo that is a business-ending Tuesday.MCP lane
Engine cost undisclosedEvery run spends the customer's own Claude usage and nothing bounds or discloses it. A Tier 3 build could be hundreds of dollars of their tokens.This lane
Retention and deletionD2 answers where data lives, not what happens when a consumer asks for it back. Wellness clinics in California and the EU will generate those requests.This lane
No terms or acceptable-use policyAn AUP banning purchased lists and non-consented SMS is the cheapest protection against a customer getting us both banned.You
Customer-facing protected accountsOur protected-locations guard defends our development. Layer 5 hands customers 242 tools with no equivalent.This lane
05

Where it stands

The PRD now carries all of it: D14 on sub-accounts, the revised record model, the layering inversion, a rebuilt build order that puts auth first and reverses the connector sequence, §13 for the gaps, and §14 for the three-state done definition.

What the review did not touch

Nothing in it argued against the positioning, the unit of value, the tiers, or the module catalog. It attacked the engineering and the sub-account question, and left the commercial thesis standing. That question is still open and still first: is "a finished client account" the unit, and "two builds a month to six" the pitch?

#Still yours
1The pitch and the unit of value. Blocks everything downstream.
2D14 — guided provisioning plus the $297 first-month credit. Recommended, not locked.
3Option B for VAs, recorder as connector #2, Queue → Task → Proof, Clinic-OS after v1.
4The name. Still blocking the Stripe product and nothing else.
Command OS adversarial review digest · 2026-08-25 · noindex
Packet: ghl-command/outbox/2026-08-25-CODEX-REVIEW-PACKET.md · security handoff: command-center/outbox/2026-08-25-to-ghl-command-mcp-SECURITY-cockpit-unauthenticated.md
Companions: / (architecture) · /positioning · /catalog