The first security commit touched 12 files, added 1,057 lines, and started with an uncomfortable sentence: credentials had been exposed in documentation.
An AI agent had been helping us prepare setup material for a client engagement. Its job was ordinary: make the integration docs and quick-start guide useful enough that the next person would not have to ask where the database lived or how external services were configured. Instead of demonstrating configuration with placeholders, it pasted live values it had access to into files headed for the repository.
The exposure included production cloud access keys, a payment-provider token, an e-signing client identifier, and a database endpoint. Nothing had reached production yet. That mattered. It did not make the repository safe.
I set this up. I gave a documentation task to an agent with real access to the values it needed to describe accurately, and I did not put a hard boundary between “can answer configuration questions” and “can write a real value into a file that gets committed.” This material was headed for a client engagement rather than an internal scratch folder, and the values were already in tracked files by the time anyone caught it. We rotated all four credential classes anyway, on the assumption that a value in version control is compromised the moment it lands there, whether or not it ever reached the client’s eyes. I do not get to know how close it came to shipping further than it did. I only know it was already past the point I thought the risk started.
The docs looked correct, which was the problem
The initial symptom was not a failing deployment or a suspicious authentication event. It was documentation that appeared unusually complete. The AI quick-start guide, integration notes, and onboarding material all contained copyable connection details. That is exactly what a setup guide should provide, until the details are real.
The diagnostic path was short once we looked at the files as security artifacts instead of prose. We searched the documentation set, found live-looking values where examples belonged, then traced the same material across the AI README, quick-start guide, and two integration documents. The repository also contained an extracted configuration file that needed cleanup.
What looked like thoroughness was a boundary failure. The agent had enough context to answer configuration questions accurately. It did not have a hard enough rule about where that accuracy was allowed to appear.
The safe version of a setup snippet is boring:
API_KEY=YOUR_API_KEYDATABASE_URL=YOUR_DATABASE_URLThe unsafe version is more useful for about thirty seconds. Then it becomes a credential-rotation project.
Rotation came before better prose
We treated the incident as production-blocking. The status was explicit: not ready for production until the exposed values had been rotated and the documentation had been cleaned. Removing the strings from the current branch was necessary, but it was not the whole response. A credential committed to version control must be treated as compromised, even if the repository was not meant to be public and even if deployment has not happened yet.
The first action was to replace exposed examples with placeholders. The next was a rotation checklist covering each exposed credential class. Then we wrote a security alert and a production-readiness strategy so the response did not exist only in someone’s memory.
That sequence matters. Improving agent instructions first would have been backwards. Once a live value appears in a tracked file, containment and rotation are operational work. Documentation changes come after the secret is no longer trusted.
We also added a database connection test script. That was connected to the root cause. The documentation was trying to answer a recurring operational question: can this environment reach the database? A test script answers it without teaching an agent to print a connection path into a Markdown file.
The fixes that were not enough
The tempting fix was to keep secrets available to agents and rely on an instruction such as “never commit credentials.” That loses because instructions are advisory context. An agent trying to make a guide executable can still decide that a real value is the best example. The exact quality we wanted, specificity, became the failure mode.
A second option was to put secret files in .gitignore and leave the workflow alone. That protects secret files, not secrets copied out of them. This incident happened in documentation, which was deliberately tracked. Ignore rules cannot distinguish a placeholder from a token after an agent pastes it into a normal .md file.
A third option was to scan only at commit time. We should scan, but it is a backstop, not a permission model. A scanner catches known patterns after the agent has already handled the value and written it into the working tree. It also cannot make every live identifier obvious, especially endpoints and client IDs that may not match a high-confidence secret signature.
The rule we kept was stricter: agents do not receive or write real secret values into any artifact that can be committed. They can receive the configuration schema, variable names, required permissions, and a way to test connectivity that does not expose the value. They get DATABASE_URL, not the database URL. They get YOUR_API_KEY, not a key.
A boundary the repository can enforce
That rule changed the onboarding material. We separated database-access guidance from credentials, clarified the expected configuration fields, cleaned the integration docs, and rewrote the AI README with the security boundary in the workflow rather than in a footnote. The quick-start guide could still tell someone what they needed to configure. It could not become a store of production access.
The immediate remediation produced an 88-line actions checklist, a 140-line security alert, and a 306-line security strategy document. The size is not a badge of rigor. It is evidence that “replace the values with placeholders” was never the whole job.
The agent did not invent the risk. We created it by giving a documentation workflow production-grade material without constraining the output channel. Once a secret is in an agent’s context, a helpful answer can turn into a committed liability.
Now the useful detail stays. The field names stay. The setup sequence stays. The real values never enter the path that ends in git commit.