cd /news/ai-agents/when-your-coding-agent-publishes-you… · home › topics › ai-agents › article
[ARTICLE · art-143821] src=simonroses.com ↗ pub= topic=ai-agents verified=true sentiment=↓ negative

When Your Coding Agent Publishes Your Secrets: An AI Forensics, Containment and Audit Playbook

A coding agent with legitimate shell and git access bundled files containing personal data and live API tokens and pushed them to a public GitHub repository, prompting a containment playbook that prioritizes killing the agent session, preserving logs, and rotating every secret before touching git history. GitGuardian's State of Secrets Sprawl 2026 counted 28.65 million new hardcoded secrets in public GitHub commits in 2025, a 34% year-over-year jump, with AI-service secrets up 81% to over 1.27 million and commits assisted by one widely used coding agent showing a 3.2% secret-leak rate against a 1.5% baseline. The playbook warns that public pushes are scraped continuously — Palo Alto's Unit 42 documented attacks launching within five minutes of an AWS credential appearing publicly — and that leaked personal data falls under GDPR's 72-hour notification clock.

read23 min views4 publishedOct 2, 2026

Read Time: 20 minutes

TL;DR #

I was recently brought into an incident that I expect to see a lot more of: a coding agent packaged files it had access to and pushed them to a public GitHub repository. Inside were personal data and live tokens. Nobody attacked anything. The agent did something it had the permissions to do, and the internet did the rest. This is the playbook I would hand to anyone who finds themselves in the same spot: stop the agent and preserve its logs before they age out, rotate every secret before you touch the git history, accept that deleting or privatising the repository does not delete the data, clean up what you can with GitHub, audit what the leaked tokens were used for, and handle the personal data under GDPR’s 72-hour clock. The second half is the AI forensics most teams have never done: reading the OpenAI and Anthropic usage dashboards and APIs to work out whether someone else spent your tokens, and on what.

A note on what this is. The case that prompted this article is real, and it is anonymised: no client, no agent product, no data specifics. What follows is a defensive playbook built from that experience and from public documentation, which I cite as I go. Provider consoles change; check the current docs before you rely on a menu path under pressure.

The incident class nobody drilled for #

We have twenty years of playbooks for a developer committing a password. We have almost none for the case where the developer is a piece of software with shell access, git credentials and a task to finish.

The mechanics of the case were mundane. A coding agent, working with the access it had been given, bundled up a set of files and pushed them to a public repository. Some of those files contained personal data. Some contained tokens, including API keys for AI providers. There was no prompt injection and no malicious skill involved, only an agent able to run git push against a public remote and nothing standing between that ability and the outcome.

The data says this is not a freak event. GitGuardian’s State of Secrets Sprawl 2026 counted 28.65 million new hardcoded secrets added to public GitHub commits in 2025, a 34% jump in a year, with AI-service secrets up 81% to over 1.27 million. The figure that caught my attention: commits assisted by one widely used coding agent showed a 3.2% secret-leak rate, against a 1.5% baseline across all public commits. That is vendor research and one data point, but it matches what I see: agents move faster than the hygiene around them. The same report found over 24,000 secrets in MCP configuration files alone. I have written before about weaponised agent skills and the dependency trap in AI-generated code; this case is the less glamorous relative of both, because it needs no adversary at all.

The clock you are actually on #

One fact decides the order of the steps: the race is against automation, not against a person who might stumble on the repository.

Public pushes are scraped continuously. Palo Alto’s Unit 42 documented a campaign that could launch a full attack within five minutes of an AWS credential appearing in a public GitHub repository, and Clutch Security’s later research found exposed AWS keys exploited within minutes. Assume that by the time a human noticed the push, every secret in it had already been harvested. From that assumption, the order of operations follows.

Step 0: Stop the agent and preserve the evidence #

Kill the agent session. Revoke the git credential it pushed with (the SSH key, personal access token or app installation) so a retry loop or a scheduled task cannot push again while you work.

Then preserve evidence, today, before it disappears on its own. The agent’s transcript is your best record of what it did and why: which files it read, which commands it ran, what it pushed and where. Those transcripts are not kept forever. Claude Code, for example, stores sessions as JSONL under ~/.claude/projects/PROJECT/ and deletes them after 30 days by default (the cleanupPeriodDays setting). OpenAI’s Codex CLI keeps its session logs as JSONL under ~/.codex/sessions/. Whatever the agent, find its session store and copy it somewhere safe, along with the shell history, the local repository with its git reflog, and any CI logs if the push came from a pipeline. Hash the copies. You may need them for a regulator, a customer or a court, and an incident report that says “the logs had rotated” is not one you want to write.

Step 1: Work out exactly what leaked #

Do not guess from memory. Clone the public repository into an isolated location and scan the full history, not just the current tree:

git clone --mirror https://github.com/
<org>/<repo>.git leaked.git
gitleaks git -v --report-format json --report-path gitleaks.json leaked.git
trufflehog git file://leaked.git --results=verified,unknown

Gitleaks gives you breadth; TruffleHog can check which credentials are still live, which tells you what to rotate first. Then go through the files by hand for personal data, because scanners are good at tokens and bad at a CSV of customer names.

Build one inventory table and keep it for the rest of the incident: each secret (type, provider, scope, owner, first commit it appears in) and each file of personal data (categories of data, approximate number of people, whose data it is). Everything after this step works from that table.

Step 2: Rotate everything before you touch history #

This is the step teams get backwards. The instinct is to delete the file, force-push and feel better. GitHub’s own guidance on removing sensitive data is blunt: if the sensitive data is a secret, “as a first step you need to revoke and/or rotate that secret.” A rotated secret is harmless wherever copies of it survive. A secret scrubbed from history but still valid is still a working key in someone else’s harvest.

Order the rotation by blast radius. Admin and organisation-level keys first (an AI provider admin key, a cloud root or IAM key, a GitHub token with org scope), because those let an attacker create new credentials that survive your rotation. Then production service keys, then everything else. After each rotation, confirm the old credential is actually dead, not just that the new one works.

Two AI-specific notes. First, GitHub runs a secret scanning partner programme: when it detects a partner’s key in a public repository it notifies the provider, which may revoke it. Anthropic states that it automatically deactivates Claude API keys found this way and emails the affected user. OpenAI has been a GitHub secret scanning integrator since 2021, but its key-safety guidance simply tells you to rotate a leaked key immediately. Treat provider-side revocation as a bonus, never as your control. And keep that Anthropic email: its timestamp is a useful anchor for your timeline. Second, rotating an AI key breaks whatever used it. Have the new key ready in your secrets manager before you kill the old one, or you will be doing incident response and an outage at the same time.

Step 3: Stop the bleeding, and understand what that does not do #

Now reduce further exposure: make the repository private or take it down. Do it, but be clear-eyed about what it achieves, because this is where most people’s mental model is wrong.

GitHub documents that when a public repository is made private, “its public forks are split off into a new network” and stay public. Delete it, and the oldest active public fork becomes the new upstream. Truffle Security showed in 2024 that as long as one fork exists, commits in the network remain reachable by their hash, and GitHub called that behaviour by design. Force-pushing does not help either: Sharon Brizinov scanned every force-push event since 2020 recorded in the public GH Archive, pulled the “deleted” commits back by their SHA, found thousands of live secrets and collected around $25,000 in bug bounties. The push event, with its commit hash, is in a public archive that you do not control.

So containment of the data is partial at best. Containment of the secrets is total, if you did Step 2. That is why Step 2 comes first.

Figure 1. Why “we deleted the repo” is not containment. An attacker needs only one route to the data (the live repository, a fork or clone taken before takedown, a dangling commit fetched by its SHA from GH Archive, or the wider fork network) and only one way to use it. The only branch you can close completely is the credential: once rotated, every copy of it is worthless. The personal data has no equivalent of rotation.

Before you flip the visibility, record what you can see. Under Insights → Traffic, GitHub shows clones and visitors for the past 14 days (UTC, updated hourly) to anyone with push access. Screenshot it, note the fork list and the stargazers. It will not tell you who, but it tells you whether anyone did, which matters a great deal for the personal-data assessment in Step 6.

Step 4: Clean up what GitHub can clean up #

With secrets dead and the repository private, rewrite history properly. GitHub recommends git-filter-repo 2.47 or later with its sensitive-data mode:

git-filter-repo --sensitive-data-removal --invert-paths --path path/to/leaked-file
git-filter-repo --sensitive-data-removal --replace-text ../patterns.txt

Then contact GitHub Support with the repository name, the number of affected pull requests and the “first changed commits” that git-filter-repo reports, so they can remove cached views, dereference pull request refs and garbage-collect. For forks, the private information removal policy covers access credentials and third-party tokens, and personal identifiers such as government ID numbers; GitHub does not disable forks automatically, so list every fork you know of in the request, with file links, line numbers and why each item is a risk. Wider categories of personal data may not fit that policy cleanly, which is one more reason the GDPR assessment in Step 6 must assume exposure rather than hope for removal.

If you want to know what an outsider can still reach, run the same tooling an attacker would: TruffleHog’s github-experimental --object-discovery mode enumerates hidden and deleted commits in a repository network. It is slow and rate-limited, but it answers the question honestly.

Step 5: Audit what the tokens were used for #

Rotation stops future misuse. It tells you nothing about what happened between the push and the rotation. For every live secret in your inventory, pull the provider’s logs for that window: cloud audit trails, GitHub’s audit log, database access logs. For AI keys, that means the provider dashboards and usage APIs, which most teams have never looked at with an investigator’s eye. That is the AI forensics part of the job, and the second half of this article.

One question to answer first: did any leaked key have admin rights? An admin key lets the attacker create new keys, add users or change settings, which is how you end up rotating the leaked key and still bleeding. For those keys, the audit log comes before the usage report.

Step 6: The personal data and the 72-hour clock #

A public repository containing personal data is a personal data breach under GDPR: a loss of confidentiality, whether or not you can prove anyone downloaded it. Article 33 requires notifying the supervisory authority “without undue delay and, where feasible, not later than 72 hours” after becoming aware of it, unless the breach is unlikely to result in a risk to people’s rights and freedoms. Article 34 adds communication to the affected individuals when the risk is high. And Article 33(5) requires you to document the breach, its effects and your remedial action even if you decide not to notify.

In Spain, the AEPD provides Asesora Brecha to help decide whether to notify and Comunica-Brecha for the communication to individuals, alongside its notification guidance. The clock starts when you become aware, not when you finish investigating; you can notify in phases. Your Step 1 inventory and your Step 3 traffic screenshots are exactly what the notification asks for. And if you read my last article, you will know the AEPD has already received its first notification of a breach executed by an AI agent. An agent causing a breach by publishing data is a different case from an agent attacking you, but the regulator is clearly paying attention to both.

Assessing token use in the OpenAI dashboard #

If an OpenAI key leaked, you want three answers: was it used after the push, for what, and did anyone use it to create more access.

The usage dashboard. At platform.openai.com/usage (organisation owners, or users with the Usage Dashboard permission), the bar along the top sets the scope: API sources, project, API keys and date range. Pick the affected project, narrow to the leaked key with the API keys selector, and set the range to cover a baseline week before the push and everything after it. Data is shown in UTC, and usage detail pages let you drop to a 1-minute interval, which is where abuse shows up as a wall of tokens at 3 a.m. Two parts of the page matter most in an incident. The API capabilities tab gives one card per service (Responses and Chat Completions, Images, Web Searches, File Searches, Moderation, Embeddings and more), so a key that has only ever done text and suddenly shows image requests stands out at a glance. The panel on the right breaks requests down by user, service and API key, and shows the month’s spend against your budget. The download button exports the data for your case file.

Figure 2. The OpenAI usage dashboard, filtered to one project over 30 days. Check every card in the API capabilities tab, not just Responses and Chat Completions: abuse often shows up in a service your application never uses. The panel on the right splits requests by user, service and API key, and shows spend against the monthly budget. Organisation and user names redacted.

Per-key numbers through the Usage API. The dashboard is good for eyeballing; for evidence, use the Usage API with an Admin key, which can group by api_key_id, project_id, model and more:

curl "https://api.openai.com/v1/organization/usage/completions?\
start_time=
<unix_ts>&bucket_width=1h&group_by=api_key_id&group_by=model&limit=168" \
  -H "Authorization: Bearer $OPENAI_ADMIN_KEY"

You get input_tokens, output_tokens, input_cached_tokens and num_model_requests per bucket, per key, per model. Separate endpoints cover embeddings, images, audio, web search and more, and /v1/organization/costs gives spend. Check them all: an attacker burning your key on image generation will not show up in completions.

Figure 3. The Spend categories tab splits cost per model into cached input, input and output, plus tool calls such as web search. A jump in output cost relative to input is one of the signals described below. Organisation and user names redacted.

When was the key last used? The API keys page answers this directly: every key has a Last used column next to its creation date, expiry, creator and permissions, and an API Key Usage button at the top of the page links to usage. The Admin API exposes the same value as last_used_at on the project key object. If the date is later than your rotation, you rotated the wrong key. If a key you had forgotten about shows recent use, start there.

Figure 4. The OpenAI API keys page. The Last used column is the fastest answer to “was this key used after the leak?”. Note the Expires column reading Never: a leaked key with no expiry stays valid until someone rotates it. Key names, tracking IDs, key hints and creator redacted.

Did anyone create more access? This is what the Audit Logs API is for: API key creation, updates and deletion, user and service account changes, login failures, project and settings changes, with event types such as api_key.created. The catch: audit logging only records from the moment an owner enables it (organisation settings → Data controls → Data retention), and once on it cannot be switched off without contacting support. If it was not enabled before the incident, you have no history. Enable it today.

What did they ask for? Responses API calls are stored for 30 days by default unless your organisation has Zero Data Retention, and you can browse them under Logs in the dashboard, with separate tabs for Responses, Agents, Realtime, Completions, Conversations and more. Filter to the exposure window and look for prompts, models or tools you do not recognise. Opening a single entry shows its timestamp, model, token count and the tools it used, which is often enough to tell your application’s traffic from someone else’s. The dashboard itself now warns that Responses logs older than 30 days will soon no longer be available, so export what you need on day one. Beyond that, OpenAI keeps abuse-monitoring logs for up to 30 days; if you need content or source details you cannot see, open a case through the help centre quickly, before that window closes.

Figure 5. The Logs page, Responses tab: one row per stored call, with its model and timestamp. Read the banner: logs older than 30 days are going away, so export early. Prompt text redacted.

Figure 6. A single logged response. The properties panel gives the timestamp, model, token count and tools used (here, web search): per-request detail the usage charts cannot give you. Prompt and response ID redacted.

Assessing token use in the Anthropic Console #

The questions are the same; the tools differ.

Check your inbox first. If GitHub’s partner scan caught the key, Anthropic will already have deactivated it and emailed you. That email gives you an upper bound on the exposure window for that key.

The Console pages. Under Analytics, the Console has Usage and Cost pages. Both report in UTC, filter by workspace, API key and model, group the chart by model, and export the data with the download button; Usage also filters by account. Anthropic’s own advice is to review usage patterns per key regularly. During an incident, select the leaked key, set the range across your baseline week and the exposure window, and group by model. Usage gives you tokens in and out; Cost splits the money into tokens, web search, code execution and session runtime. The same Analytics menu also has a Logs page. I could not find public documentation of what it records, so open it early in the incident and check whether it covers the window you need.

Figure 7. The Claude Console Usage page over 30 days, grouped by model. Select the leaked key in the API key filter and compare the exposure window with the days before the push.

Figure 8. The Cost page uses the same filters and splits spend into token, web search, code execution and session runtime costs. A model your team does not use appearing in the legend is worth a closer look.

Looking at a previous month. The Cost column on the API keys page only covers the current month. On the 1st it goes blank, and a key that was busy last month suddenly looks unused. To see an earlier month, open Cost, set Range to Last month (the arrows next to it step back one month at a time) and switch the chart to the table view for a day-by-day breakdown. The download button exports the same period broken down by key, day, model and token type, with each key’s ID and status. The Cost API cannot give you that, because it only groups costs by workspace or model, so make this export one of the first things you save.

Figure 9. The Cost page set to Last month, in table view: one row per day, one column per model, and the billed total. Use the arrows next to Range to step back to the month of the leak.

Summarised by key, that export for one of my own accounts over September looks like this (key names replaced):

Key Status September cost Days with spend First and last day
Key A Disabled $104.87 17 7 → 25 Sep
Key B Active, created 25 Sep $21.49 4 25 → 30 Sep
Key C Active $10.98 5 9 → 18 Sep
Key D Active $0.11 2 7 → 14 Sep
Total $137.45

Two things are worth reading from it. Key A, now disabled, goes quiet on 25 September, the same day Key B was created and starts spending. That is the pattern you want to see after a rotation: the old key stops on the day you disable it and the new one takes over. A disabled key that still shows spend after its switch-off date means you disabled the wrong key, or it is not really dead. The second thing is the mix: about two thirds of the month (around $89) went on writing to the prompt cache, which is normal for an agent working with long contexts. Your usual mix of token types is a fingerprint of your own workload, and someone abusing a stolen key with short, uncached prompts would leave a very different one.

Per-key numbers through the Usage and Cost API. With an Admin API key (sk-ant-admin01-…, created by organisation admins), the usage report groups by api_key_id, workspace_id, model and more, in 1-minute, 1-hour or 1-day buckets, and data typically lands about five minutes after the request:

curl "https://api.anthropic.com/v1/organizations/usage_report/messages?\
starting_at=2026-09-01T00:00:00Z&ending_at=2026-09-25T00:00:00Z&\
bucket_width=1h&group_by[]=api_key_id&group_by[]=model" \
  -H "anthropic-version: 2023-06-01" \
  -H "x-api-key: $ANTHROPIC_ADMIN_KEY"

The response separates uncached_input_tokens, cache_read_input_tokens, cache_creation_input_tokens, output_tokens and server tool use. /v1/organizations/cost_report gives the money. If your developers use Claude Code under the organisation, the separate Claude Code Analytics API breaks cost down per user.

Disable and inventory keys. In the Console, Organization settings → API keys lists every key with its linked account, workspace, expiry, creator and cost, and filters by creator, linked account, status and workspace. The menu at the end of each row disables a key, and later re-enables or deletes it. The Admin API does the same programmatically: it lists keys and can set a key’s status to inactive or archived. It cannot create keys, so if your inventory shows a key nobody recognises, it was created through the Console, which points to a compromised account rather than a leaked key. That changes the scope of the incident.

Figure 10. Organization settings → API keys in the Claude Console. The dimmed row is a disabled key; its menu offers to re-enable or delete it. The Created by and Status filters help you spot keys nobody remembers creating, and the Expires column shows which keys will die on their own. Key names, key hints, linked accounts and creators redacted.

Who changed what. The Compliance API’s Activity Feed, readable with an Admin API key from the Console where the Compliance API is enabled for your organisation, records administrative actions such as creating an API key or adding a member to a workspace. Anthropic is explicit that it does not log inference activity, so it will tell you if someone minted access, not what they asked the model. For request-level questions, go to Anthropic support.

Reading the numbers like an investigator #

Neither provider hands you an “attacker” label. You are looking for usage that does not fit your own pattern, so always compare the exposure window against a baseline from before the push. The signals I look for:

  • Traffic on a key after its rotation time, or on a key that should have been idle (a dev key, an old project key).
  • Models your team does not use, especially the most expensive ones, or new services (image, audio, batch) appearing on a key that only ever did text.
  • A jump in output tokens relative to input, which looks like bulk generation rather than your application’s usual pattern.
  • Activity in hours or at a steady machine rate that matches nobody’s working day.
  • New keys, service accounts, members or workspaces you did not create, in either audit log.

What you will not get from either dashboard is a source IP. Usage is aggregated by key, model and time, not by caller. If you need to attribute or prove access, that is a support request, and it is time-sensitive.

The part that stops the next one #

Containment is the urgent part. The root cause is almost always the same: the agent had more reach than its task needed. The fixes are not exotic:

Take public publishing away from the agent. It should not hold a credential that can push to a public remote, change repository visibility or create repositories. Use a fine-grained token scoped to the one repository it works on, and require a human for git push and anything touching visibility (every serious coding agent supports deny rules or approval prompts for specific commands).

Keep secrets out of the agent’s working directory. No .env files with production keys where the agent can read them, no admin keys in its environment. If it cannot read the secret, it cannot publish it.

Turn on push protection and pre-commit scanning. GitHub’s push protection and a gitleaks pre-commit hook would very probably have blocked the token part of this incident before it reached GitHub, as long as those token types are among the patterns they recognise. They would not have caught a spreadsheet of personal data, which is why the first two controls matter more.

Scope and cap your AI keys. One key per project and environment, budgets and spend limits set, admin keys used only by the people who need them. A leaked project key with a spend cap is an annoyance; a leaked admin key is an incident.

Keep the evidence longer than the default. Raise the agent’s transcript retention (30 days is too short for an investigation that starts on day 25), and switch on OpenAI’s audit logging and Anthropic’s Activity Feed collection now, while you have nothing to investigate.

The checklist #

For the day it happens to you, in order:

  1. Kill the agent session and revoke its git credential.
  2. Copy the agent transcripts, shell history, local repo and reflog; hash them.
  3. Mirror-clone the public repo; scan full history; build the secrets and personal-data inventory.
  4. Rotate every secret, admin keys first; verify the old ones are dead.
  5. Screenshot traffic, forks and stars; then make the repository private.
  6. Rewrite history with git-filter-repo; contact GitHub Support; file removal requests for known forks.
  7. Pull provider logs for every leaked secret: usage, audit, activity. For AI keys, use the dashboards and APIs above.
  8. Run the GDPR assessment; notify within 72 hours of awareness if required; document either way.
  9. Fix the agent’s permissions before you let it run again.

So what #

The uncomfortable lesson of this case is that no one did anything malicious, and it still became a breach with regulatory consequences. We spent the last two years worrying about agents being turned against us by prompt injection and poisoned tools. Those threats are real. But the more common failure is simpler: we gave software the same credentials and reach as a senior engineer and none of the judgement, then pointed it at a deadline.

Every team running coding agents should rehearse this incident before it happens. Pick a test repository, plant a canary token, let the agent push it, and time how long it takes you to rotate, audit and answer the regulator’s questions. If you cannot read your AI provider’s usage by key today, when nothing is on fire, you will not learn it at 2 a.m. with the 72-hour clock running.

Stay paranoid. Rotate first. And never give an agent a push it does not need.

Further Reading:

Questions or feedback? Reach out via:

Contact: info@vulnex.com

── more in #ai-agents 4 stories · sorted by recency
── more on @github 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/when-your-coding-age…] indexed:0 read:23min 2026-10-02 · —