Skip to content
Security case study / Moon Sherpa LabsMOON SHERPA / LABS

Defensive security for the AI systems we run.
Found, validated, fixed.

We build and operate multi-tenant AI products, MCP servers, and records systems that hold sensitive personal data. This case study documents the vulnerabilities we found and fixed in that work between July and October 2026, how we found them, and the controls we operate.

  • Prepared October 21, 2026
  • For the Anthropic Cyber Verification Program
  • Requested tier: Defense Access

At a glance

The short version.

OrganizationMoon Sherpa Labs, a software studio in Austin, Texas
Security workDefensive application security for AI systems we build and operate: code review, vulnerability analysis, and validation of findings
Requested tierDefense Access
Who would use itAustin Archuleta (founder) and staff we name in the portal, through Claude Code and the Claude API. Access is not resold or provided to clients.
Track record19 verified vulnerabilities fixed between July and October 2026: 3 critical, 9 high, 7 medium
EvidenceDiffs, tests, and audit records for every finding, available on request or in a walkthrough

Our security work is defensive application security for systems we build and operate. We are not a penetration-testing firm, and we do not resell model access or provide it to clients.

What we protect

Systems where a bug exposes someone else’s data.

Follow Up Ace

A multi-tenant AI assistant and MCP server embedded in the Follow Up Boss real-estate CRM. Many agents share one CRM account, so every read has to be scoped to the right agent, team, and tenant. It exposes 193 MCP tools to Claude and other AI clients.

Law-firm records systems

A client portal, intake platform, case tools, and an MCP connector into practice-management, accounting, and document systems, built and operated for a law-firm client (named on request). They hold medical and legal records and other highly sensitive personal data.

LocalTeam and Zabalist

Real-estate and construction-permit data platforms on Supabase with 330 migrations, row-level security, and per-tenant professional tools, including licensed listing data that must not appear on public pages.

Lead-capture websites

Public sites whose forms feed directly into the intake and CRM systems above.

How we find bugs

AI finds candidates. People decide what is real.

  1. AI-assisted review on every change

    Claude reviews code as it is written, then in a second, adversarial pass before merge. Several fixes below came from that second pass on code a first pass had already approved.

  2. Periodic multi-agent audits

    For larger systems we run a scripted audit. Agents review the codebase shard by shard, then every raw finding is challenged for truth, real-world impact, and fix quality before anyone acts on it. The October 2026 Follow Up Ace audit produced 145 raw findings; the ones that survived were fixed in a gated batch.

  3. Human validation

    A person confirms each finding against the code before a fix ships. Severity is assigned by impact on real users and data, not by a scanner score.

  4. Fix the class, then lock it in

    We fix the root cause wherever it appears, not only the reported instance, and add a regression test. For isolation bugs we add structural tests that fail the build if the unsafe pattern returns anywhere in the codebase. 14 of the 19 fixes have regression tests or a CI gate.

Findings

Twelve of the nineteen.

Every vulnerability below is fixed in code. Severities are our own assessment of impact on real users and data. Apart from the customer-reported cross-tenant room, where one customer saw drafts for another tenant’s contacts, we are not aware of any of these issues being exploited. Commit references, diffs, and tests are in private repositories and available on request.

SystemWhat the bug allowedClassSeverityFixFixed
Follow Up AceOAuth callback not bound to the user who started it, letting an attacker merge their account into a victim's tenant as an adminLogin CSRF, open redirect (CWE-352, CWE-601)CriticalFlow bound to the initiating browser; account, user, and origin checked on return; no authority without an active membershipOct 13, 2026 · test
Follow Up AceA context route trusted identity from the request body, returning any contact's record, notes, calls, and textsMissing authentication, IDOR (CWE-306, CWE-639)CriticalRoute moved behind signed CRM-context verification; mismatched identity refusedAug 23, 2026 · tests
Law-firm portalA table holding case data and several privileged database functions were reachable with the public client keyMissing authorization (CWE-284, CWE-862)CriticalRow-level security enabled, public grants revoked, functions limited to the server roleOct 12, 2026
Follow Up AceAgent-facing reads fell back to the account owner's grant, so agents could see the whole brokerage's contactsIncorrect authorization (CWE-863)HighOne credential resolver across 32 files, plus structural tests that fail the build on the unsafe fallbackAug 23 and Oct 13, 2026 · tests
Follow Up AceA real-time room keyed by an ID that repeats across tenants showed AI drafts to the wrong customerCross-tenant exposure (CWE-668)HighRoom keys include the tenant and fail closed; a sweep test caught 8 more call sitesSep 23, 2026 · test
Follow Up AceCORS reflection with cross-site cookies let a third-party page read a signed-in user's contacts or change settingsPermissive CORS, CSRF (CWE-942, CWE-352)HighRouter-level origin allowlist and a custom-header requirement on writesSep 2, 2026 · test
Law-firm portalThe verification lockout could be beaten with parallel requestsRace condition, brute force (CWE-362, CWE-307)HighAttempts reserved atomically before the check is evaluatedOct 12, 2026 · tests
Law-firm portalStaff role lookup granted admin when the lookup itself failedFail-open authorization (CWE-636)HighFalls back to the narrowest role; unknown roles rejectedOct 12, 2026 · tests
Law-firm case toolAdmin APIs returning sensitive form answers were reachable by any signed-in staff accountMissing authorization (CWE-862)HighStaff guard on every admin API, secrets removed from responses, server-side upload limitsJul 2, 2026
Law-firm recordsAn ambiguous record number could return another person's records to an AI review stepIDOR via key collision (CWE-639)HighExact typed lookups that fail closed, ID-only audit lines, and a CI-gated self-testOct 13, 2026 · CI gate
Law-firm MCP connectorOAuth bearer tokens lost their per-service scope, exposing every tool family to every loginPrivilege escalation (CWE-863)HighService scope carried through the authorization code into the issued tokenSep 14, 2026
Lead-capture websiteThe form let the browser choose the URL the server posted submissions toSSRF, open relay (CWE-918)MediumDestinations come from server configuration onlyAug 28, 2026

The other seven: hard-coded third-party credentials in upload scripts (high), and six medium issues, which were an unlimited-guess verification gate and a client-supplied storage key on intake uploads, personal data written into review flags and logs, autonomous email sending without a recorded admin opt-in, cross-user reads of private models, bot-protection tokens accepted for any hostname, and a regular expression that could stall a webhook worker (ReDoS).

Three in depth

How three of them were found and closed.

Follow Up Ace · Critical

An OAuth callback that could hand a victim's CRM to an attacker

Connecting Follow Up Ace to a CRM uses OAuth. The callback trusted its signed state to say whose account a grant belonged to, but nothing tied that state to the browser that started the flow. An admin of one tenant could send a CRM owner an authorize link; the victim's grant then landed on the attacker's account, and a reconciliation job merged the attacker into the victim's tenant as an admin. The state also carried an unchecked return origin.

Our October multi-agent audit raised it, and we confirmed it by tracing the reconciler by hand. The fix binds the flow to the initiating browser with an HttpOnly nonce whose hash is signed into the state. The callback refuses a different CRM account or user, accepts only its own origins, and a merge grants no authority without an active membership. A regression test covers each path.

Follow Up Ace · High · customer-reported

A tenant-isolation bug fixed as a class, not an instance

A customer reported seeing AI-drafted messages for contacts they did not recognise. A real-time room was named after the CRM user ID, and those IDs are only unique inside one CRM account, so agents in different brokerages could share a room.

Every room key now comes from one helper that includes the tenant and fails closed when either part is missing. A structural test scans the codebase for rooms built any other way. It flagged eight more call sites and now fails the build if the pattern returns. We confirmed the test catches the bug by reverting the fix and re-running it.

Law-firm records · High

Making an AI records reader fail closed

An AI review step and a staff assistant read records by number. Record numbers from three systems shared one number space, and a bare number matched all of them, so one lookup could return another person's file.

Every lookup now names its kind and ID exactly. A missing match returns nothing, more than one match is refused, and a bare number is rejected before any request is made. Each call writes an audit line containing IDs only, a census tool measures possible exposure as counts without reading contents, and the self-test is a required CI gate on deploys.

What we got wrong

The parts we are still improving.

A correct fix caused an outage

Removing hard-coded webhook defaults closed an SSRF path on a lead-capture site, but the production configuration had never been set, and lead delivery stopped until it was. Our release checklist now verifies production configuration for any change that removes a fallback.

Secrets reached source control

Two codebases held hard-coded credentials, a bot-protection secret and a media API key, for months before review found them. Both were moved to managed secrets and the exposed values are being rotated. Committed-secret scanning now gates CI on our two largest codebases, and we are extending it to the rest.

Scanning is not yet uniform

Static analysis and dependency alerts are not enabled on every repository yet, and some rate limits are per-instance rather than shared. Closing both gaps is our next step.

Why this access

Where our security work needs more.

Proving reachability before we fix

Our audits produce more candidates than we can confirm by hand: 145 raw findings from one audit. The step that separates real bugs from noise is demonstrating, on our own systems, that an attacker can actually reach the flaw. Writing that proof of concept is exactly the kind of work general-availability safeguards are designed to limit.

Attacking our own AI agents

Our MCP servers and agents read untrusted CRM notes, emails, and documents. Testing them means writing realistic prompt-injection and tool-abuse payloads and running them against our own servers, which is adversarial content by design.

Review at the pace we ship

About 5,500 commits landed across these codebases in six months, roughly 73% co-authored with Claude. The bugs that mattered most, such as tenant isolation, OAuth binding, and race conditions, each needed reasoning across many files and services. More capable review is how our security work keeps up with our own output.

A boundary we keep

We test only systems we own or are authorized in writing to test. We do not develop or deploy malware, run denial-of-service testing, or attack third-party platforms. Every finding is validated by a person before it is reported or acted on.

Controls

The controls we operate.

RequirementWhat we doEvidence
Defensive purposeWe test only systems we build and operate, plus client-owned systems under written authorization for code review and finding validation. We do not perform active testing of client production systems under this tier. Our customers are small businesses, law firms, and real-estate teams; we have no military, intelligence, or law-enforcement customers./security and /.well-known/security.txt
Multi-factor authenticationRequired on source control, cloud, hosting, database, email, and AI-provider accounts. We will move to phishing-resistant MFA (passkeys or hardware keys) before December 15, 2026.Account policy (attested)
Short-lived credentialsDeploys authenticate to AWS through GitHub OIDC role assumption, with no stored cloud keys. Anthropic API access will move to Workload Identity Federation before December 15, 2026; until then we operate within the program's seven-day key expiry.Deploy workflows (available on request)
Secrets handlingManaged secret stores. Magic-link tokens are stored only as peppered hashes, and OAuth tokens are encrypted with AES-256-GCM. CI blocks committed secrets on our largest codebases.Token, encryption, and secret-scanning tests
Isolation and least privilegeRow-level security on sensitive data, scoped MCP tokens with PKCE and revocation, a least-privilege public database role, and build-failing structural tests for tenant scoping.Migrations and scoping invariant tests
Audit loggingAccess audit trails for sensitive records, audit rows for agent actions and admin changes, and ID-only logging for record reads.Audit modules
AI agent safetyKill switches, a recorded human opt-in before autonomous sending, confirm-before-send, server-side compliance review, model allowlists for client data, and untrusted-data framing against prompt injection.Agent registry and MCP server instructions
Data retentionWe accept the data retention the program requires so that misuse can be monitored.Accepted

Verification

Questions or evidence requests.

Austin Archuleta, Moon Sherpa Labs · austin@moonsherpa.com. We can walk through any finding live, share diffs and tests, or demonstrate a control.

Our security policy