AWS Marketplace·Enterprise deployment — listing in progress.Contact sales·View pricing

INTERTRACE — RUNTIME AI SECURITY • GATEWAY PROTECTION • RUNTIME VERIFICATION • BEHAVIORAL INTELLIGENCE • PROMPT INJECTION DEFENSE • PII REDACTION • SUB-50MS CLASSIFICATION • COMPLIANCE REPORTING • MANAGED AGENTS • OWASP LLM TOP 10 • INTERTRACE — RUNTIME AI SECURITY • GATEWAY PROTECTION • RUNTIME VERIFICATION • BEHAVIORAL INTELLIGENCE • PROMPT INJECTION DEFENSE • PII REDACTION • SUB-50MS CLASSIFICATION • COMPLIANCE REPORTING • MANAGED AGENTS • OWASP LLM TOP 10 • INTERTRACE — RUNTIME AI SECURITY • GATEWAY PROTECTION • RUNTIME VERIFICATION • BEHAVIORAL INTELLIGENCE • PROMPT INJECTION DEFENSE • PII REDACTION • SUB-50MS CLASSIFICATION • COMPLIANCE REPORTING • MANAGED AGENTS • OWASP LLM TOP 10 •
← Back

Shadow AI is not a census problem

Essay · January 22, 2026 · 14 min readBy Samuel OyanEngineer
shadow AIdiscoveryestateruntime

Most enterprises still try to inventory every model call with a spreadsheet. Discovery only becomes real when unapproved traffic hits a runtime that can name the agent, the key, and the policy that should have applied.

Security questionnaires still ask which models you use as if the answer were a static list. In 2026 the honest answer is whoever last pasted an API key into a weekend prototype, whichever SaaS quietly added a copilot, and whichever contractor pointed an SDK at a personal provider account. Shadow AI is not a missing row in a CMDB. It is traffic that never crossed a policy boundary you own.

I keep seeing the same program: a GRC owner opens a spreadsheet, a security architect schedules interviews, an application owner names ChatGPT and “the internal RAG bot,” and the slide is labeled complete. Two weeks later a designer has installed an MCP server because it worked in an IDE, a support workflow is summarizing tickets through an unmanaged key, and a batch job has swapped base_url to a cheaper provider. The census did not fail because people lied. It failed because the object it tried to count does not sit still.

This essay is about replacing that instinct with a different one. Discovery that survives Friday deploys is a side effect of enforcement. Clients that speak OpenAI-compatible /v1, MCP /mcp, Inference, Service /service/*, or A2A /a2a through a runtime you control present a key. Tenancy is derived from that key. Events land in org-scoped telemetry. Unkeyed or mis-keyed traffic is not unknown asset forty-seven. It is a failed authorization. Estate views are projections of observed traffic and registered assets, not a second database of hoped-for architecture.

The census instinct

Traditional asset inventory works when the unit of risk is a host, a binary, or a SaaS tenant with a contract. You can scan a subnet. You can pull a SCIM list. You can ask procurement for invoices. Large language model usage breaks those assumptions. The unit of risk is a call: a prompt, a tool invocation, a resource read, a message/send between agents. Calls are cheap, ephemeral, and often issued by software that was not an “AI project” last quarter. A model name on a slide does not tell you which tools it can reach, which documents it retrieved, or which organization the bearer token actually maps to.

The census instinct is understandable. Boards ask for a list. Insurers ask for a list. Auditors ask for a list. Lists are how enterprises have always proven they know what they run. The mistake is treating the list as the control. A list of declared models is a statement of intent. It is not a boundary. Intent and boundary only meet when traffic is forced through something that can authenticate a caller, name a tenant, apply a policy, and emit an event you can query later under row-level security.

I have watched teams try to close the gap with browser extensions, CASB categories, and DLP on “ChatGPT.” Those tools catch a slice of human paste-into-web-UI behavior. They do not catch the SDK in a cron job, the IDE agent on a laptop, the MCP call that never looks like a chat product, or the A2A hop that never touches a browser. Shadow AI in production is mostly machines talking to machines with keys that outlived the Slack thread that minted them.

Why inventories decay overnight

A quarterly survey captures declared intent. It does not capture the intern who swapped providers to save money, the Zapier step that now summarizes tickets, or the MCP server a designer installed because a README said it just worked. By the time GRC closes the spreadsheet, the estate has mutated. Mutation is not an edge case. It is the default lifecycle of agentic software: someone changes a base_url, adds a tool, raises a temperature, or points retrieval at a new bucket, and the risk profile of the system changes without a change-control ticket that security ever sees.

Decay has a shape. Keys outlive projects. Personal tokens become production dependencies with no owner. SaaS copilots appear as HTTPS to a vendor you already allowlisted for a different product. Local agents on laptops never show up in cloud flow logs until they call an internal tool. Retrieval corpora drift: a poisoned document in a popular index is inherited by every agent that cites it. None of that is visible in a model-name census. All of it is visible, in principle, if the call crossed a gateway that recorded the key, the plane, the tool name, and the decision.

  • Keys outlive the project that minted them. Personal tokens become production dependencies with no owner and no rotation.
  • SaaS copilots appear as HTTPS to a vendor you already allowlisted for a different SKU, so network controls stay quiet.
  • Local agents on laptops never show up in VPC flow logs until they call an internal tool or an MCP server on the corporate network.
  • Provider swaps (base_url, model id, cheaper region) change data-handling without changing the application name in the CMDB.
  • MCP list/call and A2A message/send are not “chat products”; they will not appear in a browser-extension inventory.

What shadow AI actually is

The phrase is used loosely. Sometimes it means employees using public chat UIs. Sometimes it means unsanctioned SaaS. Sometimes it means a prototype that escaped into a revenue path. For runtime security the useful definition is narrower: shadow AI is model or tool traffic that can affect your data, your customers, or your systems without presenting an identity you issued and a policy you can cite. If you cannot name the tenant, the key, and the policy that should have applied, you do not have an asset. You have a rumor.

That definition excludes a lot of theater. Blocking chatgpt.com at the proxy does not mean you discovered shadow AI. It means you moved some humans to a different UI or a different network path. The contractor’s LangChain job, the IDE agent, and the batch summarizer will keep running. Conversely, traffic that does present a runtime key and is denied is not shadow in the interesting sense: you saw it, you named it, you refused it. The interesting remainder is traffic that never arrived, or arrived with a key that does not map, or arrived on a plane you are not inspecting.

Intertrace’s production data plane is Fly agentgateway at https://intertrace.fly.dev, pinned at v1.3.1, with clients presenting Bearer itr_rt_* keys. Planes in production are LLM /v1/*, MCP /mcp, Inference, Service /service/* with a per-tenant registry, and A2A /a2a with deep inspect on message/send. Tenancy is key-derived: ExtAuthz calls POST /api/runtime/verify-key, the key maps to an organization, and subsequent events, findings, and RLS queries are bound to that org. If verify-key fails, the request does not proceed. That is the census, such as it is: every successful call has a tenant; every failed call is a signal that someone tried to speak without one.

The key names the tenant. The client does not.
ClientBearer itr_rt_*ExtAuthzverify-keyorg_idTenant ATenant BRLSA onlyRLSB only

ExtAuthz → POST /api/runtime/verify-key → org_id on the request context. Estate rows and gateway events inherit that org. A client-supplied org header is not a discovery mechanism; it is a spoofing mechanism.

Make the runtime the census

Discovery as a side effect of enforcement sounds circular until you look at what enterprises already do for HTTPS. You do not inventory TLS by asking teams which certificates they wish they had. You terminate, you authenticate, you log. The analog for AI traffic is a gateway that understands the planes agents actually speak. An OpenAI-compatible /v1 proxy that ignores MCP is a census of chat completions and a blind spot for tools. An MCP firewall that ignores A2A is a census of list/call and a blind spot for agent-to-agent delegation. The product path has to terminate the planes you claim to see.

Key-derived tenancy is what makes the analog hold. Early drafts of AI proxies trusted an org id the client helpfully sent. That produces a beautiful estate page in a demo and a cross-tenant incident in production. The client should not know, and should not need to know, the UUID of the organization. Humans see orgs through membership. Agents see a runtime key. ExtAuthz is the only promotion from bearer string to tenant. Logs must not print the secret. Dashboard queries must not “also filter by org” as a courtesy after using the service role. Isolation is a property of the row, not of the analyst’s memory.

Events have to be a contract, not a log line. Intertrace ingest uses SecurityEventEnvelope: schema version, event id, event time, tenant_id, source, correlation, and the usual actor/agent/action/policy context. Changing that shape is a coordinated change across the gateway, POST /api/gateway/event, and typed mappers. The reason to be fussy is discovery. If the envelope does not carry tenant, plane, decision, and enough identity to join to an asset, the estate page will invent structure that the traffic never had. Pretty graphs of hoped-for architecture are how census programs fail a second time, after the spreadsheet.

Naming is the hard part

Once traffic is authenticated, you still have to name what you saw. A model id is not an agent. A User-Agent string is not an owner. A GitHub repo is not a runtime identity. The useful names are: the runtime key (workload identity), the org_id derived from it, the plane and route, the tool or MCP server if any, the policy pack and authorize decision if any, and the asset record you choose to bind. Estate in the product spine is that binding made visible: observed traffic joined to registered assets, with gaps called gaps instead of filled in by a survey.

Gaps are the point. A key that is active but unbound to an owner is a finding-shaped object, or at least a Work item, not a row you hide because it is embarrassing. A plane you terminate but do not yet classify is a coverage statement, not a green check. Discovery programs die when they are scored on completeness of a list rather than on time-to-name for new traffic. Time-to-name is an operational metric: from first verify-key success (or failure) to an asset or finding a human can act on.

Planes that hide from a chat census

If your inventory program was designed around “people using ChatGPT,” you will systematically miss the surfaces that matter in 2026. MCP list/call, resource reads, and prompt fetches are a standard now; standards concentrate attack surface. Tool-return bodies are untrusted context, the same way inbound email is untrusted context. A2A message/send is a different envelope with the same inspect problem: delegated tasks inherit or fail to inherit lineage. The Service plane exists so agents can reach approved backends; if a tenant can register an arbitrary origin without policy, you have rebuilt SSRF with extra steps.

  • LLM /v1/*: OpenAI-compatible completions and related routes. Necessary, not sufficient.
  • MCP /mcp: list, call, resources, prompts. ExtMCP inspect belongs here, not in an after-the-fact SIEM parse.
  • Inference: self-hosted or pooled model servers. Same tenancy and evidence story, different upstream.
  • Service /service/*: per-tenant registry of approved backends. Not an uninspected tunnel.
  • A2A /a2a: agent-to-agent, with deep inspect on message/send. Delegation without lineage is how census rows fork.

Canonical pre-action control is POST /v1/actions/authorize plus a scoped, time-bounded execution permit. Legacy POST /v1/runtime/tool_calls/decision remains supported for older clients. Discovery cares because a chat-time classifier decision is not the same object as an action that later ran. If you only census prompts, you will report an estate of conversations. The risk lives in the tools. Permits make the action first-class so Estate and Traffic can show what was allowed to execute, not only what the model proposed.

Go enforcer behavior matters for discovery even though it looks like a reliability topic. ExtAuthz, promptGuard, ExtMCP, and authorize+permits are fail-closed. An unavailable check is a deny, not an open proxy. That means failed discovery and failed enforcement are the same event class at the edge: the call did not proceed, and you should have a signal. Fail-open “so we can still see traffic” is how you invent a census of whatever happened to get through while the interesting part was down. You will not like that census when you need it.

Estate is a projection, not a second database

The product spine runs Connect → Estate → Traffic → Work → Graph → Policies. Estate is where operators expect to see assets, agents, discovery, and shadow AI. The failure mode is to let Estate become a CMDB that humans edit until it diverges from Traffic. Registered assets are real: you need owners, protected-asset flags, and policy bindings. Observed traffic is also real. The join is the product. A row that exists only because someone typed it, with no key and no event, should look different from a row that was born from verify-key and a SecurityEventEnvelope.

Protected assets always ForceAI under ADR 0004. That is a detection rule, but it is also a discovery rule: the things you have named as protected must not take a benign-skip path. Benign skip of AI classification is opt-in (INTERTRACE_INTENT_ROUTER_ALLOW_SKIP) and default off. A census that marks an asset protected while the runtime skips it is lying in two columns at once. Discovery and detection share a kernel or they will drift.

Control-plane discovery still exists. Preloop-shaped adapters under lib/platform/preloop/ reuse onboarding and agent-lifecycle patterns: local discover/onboard for IDE agents, inventory sync, governance authoring inputs. Those adapters enrich the estate. They are not a second console, not the customer IdP, and not the authorize SSOT. Catalog feed from agentregistry is poll-only (/v0/* into /api/cron/agentregistry-sync). A catalog entry without runtime traffic is a possibility, not an observation. Keep the distinction visible or you will ship a census of YAML.

What you still have to register

Runtime-as-census does not mean you stop talking to humans. Someone still has to mint itr_rt_* keys, bind them to owners, mark protected assets, and decide which Service registry entries are allowed. Questionnaires can still ask who owns a workload. They should not ask “list every model” as if that were the control. The questionnaire that matches the architecture asks: which keys are live, which assets are protected, which planes terminate on the gateway, what happens when verify-key fails, and where events land under RLS.

Identity for humans is a different plane. Clerk owns session and organization membership on platform.intertrace.ai. Convex holds plan and feature flags. Supabase remains the security telemetry store with RLS. org_members is the dashboard and RLS source of truth; Mission Control’s organization_members stays in sync through triggers. Mixing those stores produces an estate page that cannot be queried under tenant policy, or an identity system that starts looking like a SIEM. Discovery that dumps prompt payloads into the billing plane is how you fail an audit you have not scheduled yet.

What operators should do on Monday

If you are staring at a decaying spreadsheet, do not start another round of interviews until you can answer a simpler question: what traffic would we see if we required a runtime key tomorrow? The answer is usually: some production paths that already can rotate a base_url, and a long tail that will 403. That 403 is the census starting. Treat it as an onboarding queue, not as an outage to fail open around.

  1. Issue itr_rt_* keys per workload, not a shared platform key. Point SDKs at https://intertrace.fly.dev/v1 (and the other planes they actually use).
  2. Turn off client-supplied org headers as a tenancy mechanism. If verify-key cannot map the bearer, deny.
  3. Inventory planes, not products: /v1, /mcp, Inference, /service/*, /a2a. If you do not terminate a plane, do not claim you discovered it.
  4. Join Traffic to Estate. Unbound keys and unprotected high-volume assets are the queue. Do not auto-create an incident for every finding; expose manual create and keep Work honest.
  5. Record findings with an audit story: status_change_source via withFindingChangeSource(), history in finding_status_history, attachments in finding_evidence.
  6. Keep policy packs as inputs. Readiness is computed from evidence. A mapped framework row is not proof that shadow AI is gone.

Detection quality still matters after you can name the caller. ADR 0004 freezes regex.go and riskgate pattern counts in CI. New misses go to intent-router prototypes, classifier eval, or tenant OPA—not another MustCompile. A census of regex hits is a census of yesterday’s wording. Attackers optimize for everything except yesterday’s wording. If your discovery program is a list of blocked strings, you are back to a spreadsheet with extra steps.

What this is not

This is not a claim that putting a gateway in front of /v1 completes an AI asset inventory. Traffic you never send you will never see. That is the same limitation TLS termination has for hosts that do not use your load balancer. The operational response is to make the path of least resistance the path you own: SDKs, IDE recipes, Service registry, and authorize so that going around the gateway is harder than going through it. Procurement, CASB, and laptop management still have jobs. They are complementary. They are not a substitute for key-derived tenancy on the planes agents speak.

This is also not a compliance certificate. Packing a YAML policy and mapping it to a framework does not mean you discovered shadow AI, and it does not mean you are compliant with anything. Intertrace industry-solution wording is catalog-approved and readiness is evidence-computed. Discovery coverage can be an input to that evidence. It cannot be a badge you print from a pack ID.

Closing

Shadow AI is a runtime problem that questionnaires keep trying to solve with a census. The census decays because the unit of risk is a call, keys outlive projects, and the interesting planes do not look like a chat product. The alternative is unglamorous: terminate the planes, derive the tenant from itr_rt_* via ExtAuthz and verify-key, fail closed when that mapping fails, emit SecurityEventEnvelope into org-scoped telemetry, and let Estate be a projection of what you actually saw. That is a harder sell than a pretty org chart. It is the only inventory that stays true after Friday deploys.

Continue reading

← Back to blog