[Technical]
Obscurity was your access control
Three frictions protected your engineering estate: finding a thing, understanding it, and bothering to try. Coding agents remove all three, and unlike a person they act on what they find. This paper is about what that changes and what to do about it.
[Key takeaways]
- Obscurity was never a control, but it was doing real work. Discovery, comprehension, and motivation each cost an attacker something, and agents pay none of those costs.
- The file-sharing version of this problem ends in retrieval. The engineering version ends in actuation, because a credential is a capability rather than a document.
- Secret scanning covers commit history and CI. Agents read the working tree, which is the one place it was never pointed at.
- Zero usage used to be a signal that something was safe to deprioritize. It now means reachable, unmonitored, and without a baseline.
- The controls that fit sit at the request: redact secrets before egress, bound what an agent can reach, and gate MCP scopes before a server connects.
Obscurity was doing more work than your access control
There is a version of this argument going around about enterprise file storage, and it is correct as far as it goes. Every organization has years of accumulated permission debt: folders shared with the whole company in 2019, files inherited through nested groups nobody can draw, external collaborators who were never removed. That debt was survivable because nobody could find anything. No one browses a two hundred thousand file document store. The real control was never the access list. It was friction. Point a retrieval-augmented assistant at the same store and the friction is gone, so a dormant liability becomes a live one without an attacker, a malicious insider, or a single policy violation.
The engineering version of that argument is worse, and almost nobody is making it. Your codebase, your developer laptops, and your internal services are full of artifacts that were safe for exactly the same reason: nobody was looking. A token in a .env file that was never revoked. An endpoint written for a cron job in 2021 that has served zero requests since. An internal tool somebody built in an afternoon that is now load-bearing for six people and appears in no inventory you own.
Each of those survived on obscurity. Each is now sitting inside the context window of a coding agent that reads the whole working tree, enumerates rather than remembers, and has no sense of what is not worth trying.
Retrieval and actuation are not the same failure
Start with the distinction that makes the engineering case sharper, because it changes the severity rather than the volume.
When an assistant surfaces a document somebody should not have found, the outcome is retrieval. Data moved from a place that person was permitted to read to a place that person was permitted to read. That is a real incident and a real permissions failure, but it is bounded, and the worst case is a confidentiality problem.
When an agent finds a credential, the outcome is actuation. A credential is not information, it is a capability. The agent does not learn something it should not know, it becomes able to do something it was never authorized to do. The same is true of an undocumented administrative route and of an internal tool holding a database connection. These are not documents that leak, they are levers that get pulled.
So the accurate framing is not that AI made your permission debt readable. It is that AI made your engineering debt executable. A retrieval failure ends with data in the wrong hands. An actuation failure ends with an agent authenticated to production because a helpful autocomplete found a key in a gitignored file and used it to debug a failing deploy.
Three frictions, all of them removed
It is worth being precise about what obscurity was actually buying, because the three components fail independently and each one has its own countermeasure.
1. Discovery friction
Finding an artifact used to require knowing it existed. Institutional memory was the index, and institutional memory decays: the person who wrote the route left, the runbook was never updated, the credential predates the current platform team. An agent does not rely on memory. It reads source and enumerates. Undocumented is not undiscoverable to something whose first action is to open the router file, and a name that appears nowhere in your documentation appears in your code, which is the artifact the agent actually consumes.
2. Comprehension friction
Even after finding an undocumented thing, using it required reverse engineering: what does this endpoint expect, what does this token authenticate against, what breaks if I call it. That work was slow enough to deter casual use and slow enough to leave traces. Agents do it for free and at speed, from the same source you assumed nobody would read closely, and they will infer a request shape from a handler signature without asking anyone whether they should.
3. Motivation friction
This is the component that gets overlooked and it may be the most important. A human engineer who stumbles across a credential of uncertain provenance generally leaves it alone. Trying it feels risky, the upside is unclear, and there is a social cost to breaking something. An agent has none of that. Trying is cheap, the agent is optimizing for completing your task, and it has no fear of consequences. Artifacts that were protected mainly by being not worth the bother have lost their protection entirely.
Case one: the credential nobody revoked
What made it safe. A token sits in a .env file in one repository checkout on one laptop. It is gitignored, so it never reached a commit. The service it authenticates against is internal and obscure. The person who created it has left. Its safety was never a control, it was the low probability that anyone would go looking.
What changed. Note where this artifact lives, because the location is the whole point: the working directory, not the commit history. Secret scanning was built for git history, pull requests, and CI logs, so the one place your existing tooling was never pointed at is the first place an agent reads. A coding agent takes the working tree as context. The file is in the working tree. There is no scanning gap to close here, because the scanners are correctly configured and looking somewhere else.
What the failure looks like. It is rarely dramatic. You ask an agent why the staging deploy is failing. The agent reads the config, finds credentials, and authenticates, because authenticating is the obvious next step toward the goal you set. The same artifact now produces two simultaneous failures: a live credential sitting in a prompt sent to a third party, and a privilege exercised by something that was never granted it. The second one is the one that shows up in an incident review, and the first one is the one you cannot undo.
The clean way to state the shift: a stale credential's risk used to be proportional to the chance that somebody would go looking for it. It is now proportional to the chance that an agent's context window includes the file it lives in, and that probability is approximately one.
Case two: the endpoint with no traffic
What made it safe. A route like /internal/admin/user-export exists. Its authentication was designed for its original caller, which was a scheduled job inside the same network, so it may check a shared header, or a source range, or nothing at all. It is absent from your OpenAPI spec, linked from nowhere, and serves no requests. Its protection was the cost of finding it.
What changed. Reading the router costs an agent nothing, so the cost of finding it is now zero. Two assumptions then break at once. The first is documentation as a boundary: your spec describes the surface you meant to expose, while the router describes the surface you actually expose, and the agent reads the second one. The second is topology as authentication. "Only our own service calls this" was true because of network placement plus nobody knowing, and an agent running on a developer laptop attached to the VPN satisfies neither condition while looking, to the endpoint, exactly like an authorized caller.
What the failure looks like. The acute version arrives through MCP. Somebody wraps the internal API in an MCP server so that an assistant can help triage support tickets. That is a reasonable thing to want. It also means an undocumented administrative route carrying 2021 authentication assumptions is now reachable by a language model that takes its instructions from the text of support tickets. Prompt injection stops being a content problem at that point and becomes wired directly into an actuation path, with the agent's full authority behind it.
Case three: software that did not come through a pipeline
What made it safe. Internal software used to be a project. It had a repository, a reviewer, a deployment pipeline, and somebody's name attached. The set was long, but it was finite, knowable, and owned. You could count your internal software, and someone could tell you what each item talked to.
What changed. Somebody builds a dashboard in an afternoon, points it at the production read replica, drops the URL in a channel, and by Friday six people depend on it. The rate of internal software creation has risen by roughly an order of magnitude while review capacity has stayed flat. Three properties make this worse than the shadow IT you already know how to think about.
It is born holding a credential, because it has to talk to something, and the scope is whatever was convenient at the time, since scoping is the boring part of a one-afternoon build. It is invisible to every inventory you own: not in the monorepo, not in the asset database, absent from your identity provider's application list if it has no authentication at all, and never in CI. And it frequently has a model in the loop itself, which leaves you with an unreviewed internal application that holds a production database connection and accepts free text from users.
What the failure looks like. Not an attack. Somebody asks an agent to pull the quarterly numbers, and the agent finds the tool in a local configuration file or a shell history and uses it, because it works and it is the shortest path. The tool then does what it was built to do, with permissions nobody scoped and against data nobody classified, on behalf of a caller nobody authenticated.
Zero usage used to be a safety signal
Every case above shares a property, and it inverts a triage habit that most security and platform teams rely on.
An endpoint with no traffic. A token with no authenticated use in months. An internal application nobody logs into. Under the old model, all three read as dormant, and dormant meant safe to deprioritize. There is a whole generation of backlog tickets that were correctly closed on that reasoning, because a thing nobody touches is a thing nobody exploits.
Under agents, all three read differently. Dormant now means reachable, unmonitored, and without a baseline. Reachable, because discovery no longer depends on anyone remembering. Unmonitored, because nothing that sees no traffic ever earned a dashboard or an alert threshold. And without a baseline, which is the part that really hurts: when usage goes from zero to something, there is no historical distribution to compare against, so anomaly detection has nothing to fire on. The quietest assets in your estate are the ones where a first anomalous use looks identical to a first legitimate use.
Practically, this means the sort order of your cleanup backlog is wrong. The credential that has not been used in a year is not the safe one to leave for next quarter. It is the one nobody will notice being used.
Why your existing tooling misses all three
None of this is a tuning problem. In each case the tool is looking at the right thing for the world it was designed for.
Secret scanning watches history, not the working tree
Scanners were built on the premise that a secret becomes dangerous when it is committed and shared. That premise held for a decade. An agent reads uncommitted local state, so the exposure now happens on the side of the line the scanner does not cross, and a clean scanning report is entirely compatible with a live credential sitting in an agent's context.
API inventories are built from specs, not routers
Your catalog, gateway configuration, and documentation all describe intended surface. Agents consume actual surface. Any endpoint that exists in code but not in the spec is invisible to inventory-driven review and fully visible to anything that reads the repository, which is a gap that widens every time somebody ships a route faster than they update a spec.
Asset inventories assume software arrives through a pipeline
Discovery for asset management hangs off repositories, CI, deployment targets, and identity provider application lists. Just-in-time internal software touches none of those, so the inventory is not wrong so much as structurally unable to see the category. Counting what came through the pipeline tells you nothing about what did not.
What actually helps, and what does not
The pattern across all three cases is that the artifact is discovered and used at the moment an agent makes a request. That is where a control can still intervene, and it is the only point that does not depend on having a complete inventory first.
Redact secrets before they leave the device. High-entropy strings and credential patterns are detectable in an assembled prompt, and replacing a key with a placeholder before the request is brokered onward means the model still answers and the credential never reaches a third party. This addresses the egress half of case one without asking anyone to find every .env file first.
Bound what an agent can reach. Reach limits are the control for actuation. An agent that is prevented from touching SSH keys, production configuration, and infrastructure it was never sanctioned for cannot exercise a credential it happens to find. This is the half of case one that redaction alone does not cover, and it is the only mitigation that works when the credential is legitimately present and legitimately readable.
Gate MCP scopes before a server connects. Case two turns acute through MCP, so scoring a server on its requested permissions, its code, its maintainer, and known vulnerabilities before the connection opens is the intervention that lands earliest. Inspecting tool calls after that catches the rest.
Discover new AI-calling processes behaviorally. A just-in-time internal tool that calls a model announces itself on the network: an unfamiliar process opening outbound connections to a model endpoint, from a machine where that pattern is new. Scoring destinations by domain age, request frequency, and connection characteristics finds it without a blocklist and without waiting for it to appear in an inventory it will never appear in.
What none of this does. Be clear about the boundary. A request-layer control can tell you that an agent called an internal route and can stop it from reaching a credential store. It cannot tell you that /internal/admin/user-export is the sensitive one, or that a dashboard holds a production connection. Deciding which of your internal assets matter is inventory and ownership work, and it stays yours. The honest claim is that request-layer controls buy you time and contain the blast radius while you do it, not that they substitute for it.
Four numbers to measure this week
Each of these is computable in an afternoon, and each is more useful as a trend than as a snapshot. Recompute on a fixed cadence so the counts become curves.
1. Working-tree credentials per developer machine
Count files matching .env and similar local configuration patterns present in checkouts on developer machines, then count how many contain a credential that still authenticates. The second number is the one that matters, and the ratio between them tells you how much of your credential surface is invisible to commit-based scanning. Expect the first count to be higher than anyone predicts.
2. Router routes minus documented routes
Enumerate every route your application code actually registers, then subtract the routes present in your specification or gateway configuration. The difference is your undocumented surface. Segment it by whether each route performs a write or an export, because those are the ones where discovery converts into actuation. A difference that grows faster than your service count means documentation is falling behind delivery.
3. Credentialed internal apps absent from inventory
Count internal applications that hold a credential or a database connection and appear in no asset inventory, no identity provider application list, and no deployment pipeline. Sourcing this honestly usually means asking teams rather than querying a system, which is itself the finding: if the only way to count something is to ask, you do not have coverage of it.
4. Valid credentials with no recent authenticated use
Count credentials that still authenticate but have recorded no successful use in ninety days. Under the old triage model this was your safe backlog. Treat it now as your priority queue, because these are the credentials whose use will not look anomalous against any baseline. Watching this number fall is the cleanest single measure that the cleanup is real.
Discovery is free now
Undocumented is not undiscoverable to something that reads the router file. Institutional memory was the index, and agents do not use it.
The exposure moved to the working tree
Secret scanning watches commits and CI. Agents read uncommitted local state, so a clean report and a live credential in context are fully compatible.
Capabilities, not documents
A retrieval failure ends with data in the wrong hands. An actuation failure ends with an agent authenticated to production.
Quiet assets are the risky ones
No traffic means no baseline, so a first anomalous use looks exactly like a first legitimate one. Sort the backlog accordingly.