Claude Code saves tokens to a file, and 1,122 repositories exposed them publicly
When a developer clicks "Yes, and don't ask again" in Claude Code, the agent writes the entire command to .claude/settings.local.json. If the command contained a token or password, it ends up in that file. Claude adds git protection only to the global exclude on the laptop, so the file gets committed without any trouble. According to Apiiro, 104 185 copies were found in public repositories, and 1 122 of them contained at least one secret.
AI-processed from GitLab Blog; edited by Hamidun News
A familiar situation: the agent asks permission to run a shell command, you're tired of confirming the same thing, and you click "Yes, and don't ask again." Claude Code remembers the choice and writes the command to the .claude/settings.local.json file inside the project. The problem is that the command is saved verbatim. If you called an API via curl with a PRIVATE-TOKEN header, the token ends up in that file in plain text.
Why the file ends up in the repository
To keep the file out of git, Claude Code adds it to the developer's personal global exclude, not to the project's .gitignore. That protection lives on a single laptop and does not travel with the repository. A colleague, a CI environment, or a second computer knows nothing about this exclusion. git add . picks up the file, and it goes wherever the repository is hosted.
The company Apiiro assessed the scale in early November 2026. Public repositories contained 104 185 copies of the file, and 1 122 of them held at least one secret. Hundreds more secrets remained only in the history, in files that had already been deleted. Deleting the file does not undo the leak: the token still sits in old commits.
The GitLab guide is written for people who host their code with GitLab, but the logic carries over to any git hosting. GitLab also states that its own infrastructure secrets are protected.
How to close the gap: four layers
You configure the group once, and after that the protection works on its own. The layers run from simple to complex.
1. .gitignore. The cheapest option: add the line .claude/settings.local.json to every repository. On the Premium and Ultimate tiers, you can put it in the .gitignore of the group's template project, and all new projects will inherit it automatically.
2. Push rule (Premium and Ultimate). In the group settings, under prohibited filenames, enter the regular expression (^|/)\\.claude/settings\\.local\\.json$. Any push containing that file, whether in the root or in a subfolder, on any branch, will be rejected, and the developer will see a message with the pattern.
3. Secret Push Protection (Ultimate). This layer looks at the contents of the push, not the filename. If it finds a token in a known format, it rejects the push. It also helps when a secret lands in a file with a different name.
4. Scanning policy (Ultimate). A policy at the top level of the group runs Secret Detection in every project, even without a .gitlab-ci.yml, and individual projects cannot disable it. One rule fires on every pipeline, the other on a weekly schedule. With the variable SECRET_DETECTION_HISTORIC_SCAN: 'true', the scheduled run checks the entire history, not just new commits.
Pitfalls
Push rules have two limitations. First, they only check the addition of files, not changes to files that are already tracked, so you need to remove existing copies first. Second, a group rule is copied only to projects created after it appears. For older projects, you have to set it separately, for example via the API: POST /api/v4/projects/<id>/push_rule with the file_name_regex parameter. If a project already has its own rules, use PUT, otherwise you will get the error 422 Project push rule exists.
How to tell whether you have a problem
First you need a list of affected repositories. There are three ways to get one:
- GitLab Duo Agentic Chat. You ask the chat to find all projects in the group that ever contained this file, including deleted copies and other branches. For a group of about a hundred projects, this takes a few minutes. But it finds what search cannot see.
- Code search (Premium and Ultimate). It works fast, but sees only the default branch in its current state. The syntax depends on the engine: for exact code search, use
file:\\.claude/settings\\.local\\.json$with regular expressions; for advanced search, usefilename:settings.local.json path:.claude. In the API, keepexclude_forks=false, otherwise forks will drop out of the results. - Commits API (all tiers). A request to
repository/commits?path=.claude/settings.local.json&all=truefor each project shows whether the file existed on any branch. An empty response means the file never existed. Forks are checked the same way as regular projects.
What to do in practice
Start by searching the history, not the current state: deleted files are the most dangerous because people forget about them. Treat everything found in the file as compromised and reissue it, even if the repository is private. Then turn on protection: at least .gitignore and a push rule, and on Ultimate, content scanning as well.
One more point for teams rolling out agents: the "don't ask again" button turns a one-time permission into a permanent record of commands, and any secrets in those commands get saved along with them. Don't pass tokens to the agent as command-line arguments. Use environment variables or a secrets manager, and the saved command will be safe.
Limitation: in the text provided, the guide cuts off before the section on bulk cleanup, so for the history cleanup procedure you will need to consult the original.
- Nov 12, 2026Fable 5.2 in llm-anthropic: max mode burned 128,000 tokens and returned no answer
- Nov 12, 2026Simon Willison built a beep finder with Claude and located the source to within about 40 cm
- Nov 12, 2026AWS Opens 23 Ready-Made GenAI Components: DoorDash and Ricoh Cut Development Hours by 90%
- Nov 12, 2026Claude Fable 5.2 removes duplicate code more often when you put it in charge
Key Questions Answered
Why the file ends up in the repository?
To keep the file out of git, Claude Code adds it to the developer's personal global exclude, not to the project's .gitignore. That protection lives on a single laptop and does not travel with the repository. A colleague, a CI environment, or a second computer knows nothing about this exclusion. git add . picks up the file, and it goes wherever the repository is hosted. The company Apiiro assessed the scale in early November 2026.
How to close the gap: four layers?
You configure the group once, and after that the protection works on its own. The layers run from simple to complex. 1. .gitignore. The cheapest option: add the line .claude/settings.local.json to every repository. On the Premium and Ultimate tiers, you can put it in the .gitignore of the group's template project, and all new projects will inherit it automatically. 2. Push rule (Premium and Ultimate).
What are the key details on Pitfalls?
Push rules have two limitations. First, they only check the addition of files, not changes to files that are already tracked, so you need to remove existing copies first. Second, a group rule is copied only to projects created after it appears. For older projects, you have to set it separately, for example via the API: POST /api/v4/projects/<id>/push_rule with the file_name_regex parameter. If a project already has its own rules, use PUT, otherwise you will get the error 422 Project push rule exists.
Build & Ship Production AI Agents Without Boilerplate Code
Start your free 72-hour trial at Hamidun Academy (academy.hamidun.com) — interactive lessons on Claude Code & Cursor, live Tech Radar, and production agent templates.
The AI world, distilled — once a week
Seven stories that actually mattered, hand-picked. No noise, no reposts, no press releases.
Done! Check your inbox for a confirmation.