← Back to Blog

Leaked API Keys Get Used in About Two Minutes, and Deleting the Commit Changes Nothing

· 43 min read Leaked API Keys Get Used in About Two Minutes, and Deleting the Commit Changes Nothing

On June 25, 2024, Permiso's research team placed a working AWS access key in a file on GitHub. Attackers were trying it against Amazon Bedrock within minutes, and after one of them scaled up on August 5 the honeypot logged more than 75,000 successful model invocations in two days, almost all of it sexual roleplay (Permiso, October 3, 2024). Orca Security saw a key used within two minutes of landing on GitHub (Orca Security, June 20, 2023). Palo Alto Unit 42 watched AWS attach its quarantine policy at 13:30:22 and the attacker start reconnaissance at 13:34:15, inside five minutes of exposure (Unit 42, October 30, 2023). Clutch Security ran eleven GitHub scenarios; the ten that were exploited averaged 6.6 minutes to first use, fastest one minute, with AWS's own alert arriving in 1.4 minutes on average across all eleven (Clutch Security, December 3, 2024). Cybenari's chart reads 127 seconds (Cybenari, August 8, 2024, tabulated by ThreatDown), and Comparitech's 2020 honeypot logged use within one minute, then more than 1,000 RunInstances calls within a minute once a key with EC2 rights went up (Comparitech). The other half of the clock runs slower: a study presented at NDSS 2019 tracked leaked secrets for 16 days and found only 19% ever removed (NDSS 2019 paper), and GitGuardian retested credentials valid in 2022 and found more than 64% still valid in January 2026 (GitGuardian, March 17, 2026). Minutes to abuse, years to removal. A committed secret is already stolen, so the plan below is not "clean it up." It is "know every copy so you can revoke it first."

What the honeytokens actually measured, and what they did not

Every one of those clocks was an AWS key. Thinkst's free Canarytokens catalog, the lure Cybenari planted and Clutch used for its Docker Hub scenarios, mints AWS, Azure, CrowdStrike, Kubeconfig and MCP-configuration lures and no OpenAI, Anthropic or Google AI key (Canarytokens docs), which helps explain why no published minutes-to-abuse figure exists for an LLM key. The measurements say one to seven minutes on GitHub, not the uncited "seconds" in GitHub's own April 1, 2025 post (GitHub).

Where the key sat Fastest measured use AWS alert Source
GitHub commit or PR 1 minute fastest, 6.6 minutes average 1.4 minutes average Clutch Part 1
GitHub, RDS credentials in .env 5 minutes to a database login not stated Clutch Part 5
Docker Hub 1 minute (env var), 3 minutes (image layer) none (canary tokens) Clutch Part 2
PyPI 40 seconds (after a scanner mirrored it to GitHub); 2 hours 43 and 46 minutes Clutch Part 2
npm no exploitation in three scenarios 1 minute in two; about 8 minutes, inferred from a policy event, in the third Clutch Part 2
Pastebin 9 hours (Clutch); 50 minutes (Cybenari chart) none Clutch Part 3
GitLab 2 of 8 scenarios, both about 3 days none Clutch Part 1
S3 bucket; ECR registry 8 hours; 4 months only GitHub triggered lockdown Orca

One row carries a footnote. Cybenari's prose says its npm token was grabbed in under a minute; Clutch replicated the setup, saw one hit from an AWS-internal address and nothing across 62 downloads in a week, and concluded the hit was AWS's own quarantine process (Clutch Part 2). The numbers are a floor on attacker interest, not a ceiling on speed: GitHub's public Events API carries a stated latency of 30 seconds to 6 hours (GitHub REST docs), and Truffle Security showed in February 2024 that Thinkst canary keys can be identified statically from the AWS account ID inside them, and so can be skipped (Truffle Security).

For agent surfaces there is no minutes figure at all; the one tool built to time a key planted for an agent to find, HoneyMCP, has published no observations, and its repository was last pushed 2026-05-06 (GitHub API). GreyNoise ran three MCP honeypots, one with a deliberately exposed key, and reported discovery within days and no MCP-specific exploitation (GreyNoise, November 5, 2025). What has been measured is the scanning: a small web host logged MCP handshakes from 49 distinct addresses in two weeks plus GET requests for /.claude/mcp.json, /.cursor/mcp.json and /.vscode/mcp.json (SANS ISC diary 33150). Read together, the studies give three clocks: on GitHub it is minutes; on an exposed endpoint it is days to discovery and then hours to the first validation attempt (Pillar's figure, below); and for a key type no partner program covers, it can be months.

The scale, with whose count it is

Two vendors publish annual counts and they measure different things. GitGuardian counts new hardcoded secrets in public GitHub commits with its own detectors: 12.8 million in 2023 (2024 report), 23.8 million in 2024 (2025 report) and 28.65 million in 2025 from about 1.94 billion commits, a 1.5% baseline (2026 report). GitHub's "more than 39 million" is a secret-scanning detection count footnoted to Octoverse 2024, whose year runs October 1, 2023 through September 30, 2024, and which GitHub does not split by repository visibility (Octoverse 2024); secret scanning runs on private repos only with paid Secret Protection (GitHub docs). GitHub has published no successor figure; the 19 million it reported for 2025 is secret exposures blocked among customers using Secret Protection, across nearly 2 billion pushes, a third measure, blocks rather than leaks (GitHub, April 14, 2026).

The AI slice grows fastest. GitGuardian counted 1,275,105 AI-service secrets in 2025, up 81%, including 113,000 DeepSeek keys; 24,008 unique secrets in MCP-related configuration files, 2,117 valid (8.8%), with Google API keys (19.2%) and PostgreSQL connection strings (14%) the top valid types; and a 3.2% leak rate in Claude Code-assisted commits against the 1.5% baseline, a gap its own post says should not be read as a simple tool failure (GitGuardian blog, report page).

Persistence is the number that changes the plan. GitGuardian's 2024 report found over 90% of exposed secrets still active five days after leaking (GitGuardian). Truffle Security's August 2026 launch post headlines 64,024 AWS keys with 88% active; its companion research post gives the real denominators, and I read both: 88% of 10,616 re-verified keys (about 9,300 live), a five-year median age and 13.7% rotation rate from 2,903 keys, and 84% full-admin from 1,157 users Truffle itself calls skewed (launch post, research post). The launch post also reports 929 keys AWS had quarantined, some more than three years earlier, every one still authenticating. In three commonly-forked public repositories of one large AI company, Truffle found 40 valid API keys in deleted forks (Truffle Security, July 24, 2024).

What a stolen key is used for: the LLMjacking economics

Sysdig named the pattern on May 6, 2024. The intruders probed Bedrock with InvokeModel calls that set max_tokens_to_sample to -1 so the resulting ValidationException, rather than an AccessDenied, confirmed the credentials could reach an enabled model, and called GetModelInvocationLoggingConfiguration to learn whether prompts were logged; the open-source reverse proxy they used refuses AWS keys with logging enabled, so its victims cannot see the prompts (Sysdig). Read that as an instruction: turn Bedrock model invocation logging on. Permiso reports that none of its client victims had it enabled (Permiso), and by September 2024 Sysdig was watching attackers call DeleteModelInvocationLoggingConfiguration to switch it off and PutFoundationModelEntitlement to enable models the victim never turned on; CloudTrail still records both calls, which makes those two event names worth an alert of their own (Sysdig). The headline "$46,000 a day" is a model with its arithmetic on the page: Claude 2.x Bedrock pricing averaged to $0.016 per 1,000 tokens, times the 500,000 tokens-per-minute quota, times 60, times 24, times four regions, equals $46,080 (Sysdig). The September 18, 2024 update reprices it at over $100,000 a day for Claude 3 Opus, still as potential cost, and reports what was observed: more than 85,000 requests against one credential set, 61,000 in a three-hour window on July 11, 2024, about 95% roleplay (Sysdig).

The one observed dollar figure is an aggregate. Sysdig's February 7, 2025 report shows a snapshot of one exposed reverse-proxy instance: 395,555 seconds of uptime (about 4.5 days), 132,651 prompts, 2.213 billion tokens, 32 OpenAI keys across 13 organizations plus two Google, one AWS and one Azure key. Sysdig recomputed the cost at $49,595.83, Claude 3 Opus alone at $38,951.55; access sold through a storefront at $30 per 30-day token (Sysdig). Permiso's honeypot shows the defender side: AWS opened a support case the day the key was posted, attached AWSCompromisedKeyQuarantineV2 on August 1, 2024 (which then blocked no Bedrock actions), and cut off Bedrock on August 6, about 42 days after first malicious use; five bedrock: actions joined the quarantine policy on October 2, 2024 (Permiso).

The court record is thinner than the blog posts. Microsoft's civil case against the Storm-2139 operators (1:24-cv-2323, Eastern District of Virginia) was filed under seal December 19, 2024 after Microsoft found stolen customer API keys generating prohibited content in late July 2024 (amended complaint). Microsoft's blog says the credentials were taken from public sources (Microsoft, February 27, 2025); paragraph 79 of the complaint says the precise manner of obtaining all the keys is unknown. On July 9, 2026 the court adopted the magistrate judge's report, which records that Microsoft sought only an injunction and forwent all other relief (Dkt. 71), and entered default judgment against three defendants and a permanent injunction (Dkt. 72). An injunction, not a price.

The 2026 market has moved toward exposed endpoints. Pillar Security's honeypots logged 35,000 attack sessions, 972 a day, between December 2025 and January 2026 against unauthenticated Ollama, OpenAI-compatible APIs and open MCP servers, feeding a marketplace reselling 30-plus providers at 40% to 60% off; its only clock is a 2 to 8 hour lag between a public scan hit and the first validation attempt (Pillar Security, January 28, 2026). Anthropic's September 2026 threat report says of one actor group that every key involved was stolen from customers' environments, and describes another actor's Rust scanner that validated exposed keys in public containers (Anthropic).

A WordPress developer's 2014 bill hit $5,994.08 after a stray wp-config.php.save reached a public repo, and AWS waived $5,980.70 after a "concession request" (WP Tavern). Twelve years on, a three-developer shop reported $82,314.44 between February 11 and 12, 2026 against a normal $180 a month, and Google declined to say whether it would enforce the bill (The Register); a small team's unrestricted Firebase browser key ran up more than 54,000 euros in about 13 hours and its billing adjustment was denied (Google AI Developers Forum); and a small family-run shop's unrestricted 2021 Maps key, embedded in its public site, gained Gemini access when that API was enabled on the project on December 27, 2025, then took 515,226 calls in the first hour of the June 10, 2026 attack (Google AI Developers Forum). Every one of those bills is self-reported and the Sysdig figures above are models; no AWS, OpenAI, Anthropic or Google page promises a waiver. OpenAI's key-safety article lists "unexpected charges" among your consequences (OpenAI), and Anthropic's compromised-key article says to revoke immediately and says nothing about refunds (Anthropic help).

The agent platforms are the new public repo

OpenClaw's gateway binds to 0.0.0.0:18789 out of the box (SecurityScorecard). SecurityScorecard's STRIKE team published a February 9, 2026 snapshot of 42.9K unique IPs hosting exposed control panels across 82 countries, 15.2K apparently vulnerable to remote code execution (SecurityScorecard); The Register reported the same dashboard passing 135,000 later that day (The Register), and Censys counted 21,639 exposed instances as of January 31, 2026, most still requiring a token (Censys). Snapshots, not totals. The mechanism that turned exposure into key theft was CVE-2026-25253: a link carrying a gatewayUrl parameter made the control UI open a WebSocket to the attacker and hand over its token, after which exec.approvals.set and config.patch ran host commands; fixed in 2026.1.29 (depthfirst, February 1, 2026). OX Security found the same software storing credentials in cleartext under ~/.clawdbot and cycling through five .bak copies of its config file, so a key deleted in the UI can survive in a backup (OX Security, January 29, 2026). If you run it, the checklist is short: confirm the version is 2026.1.29 or later, get the gateway off 0.0.0.0, and count those .bak files as copies. Hold onto that one; section (d) is about the copies nobody listed.

The second mechanism is the vibe-coded backend. Wiz found Moltbook's production JavaScript carrying hardcoded Supabase connection details with no Row Level Security, exposing 1.5 million API tokens, tens of thousands of emails and 4,060 private agent-to-agent conversations, some containing plaintext OpenAI keys; the fix took about three hours (Wiz, February 2, 2026). The lesson is not "Supabase keys leaked." Supabase documents its publishable key as safe to expose in a web page and source code; the secret key bypasses every RLS policy and returns 401 from a browser (Supabase docs). The failure was missing RLS. Lovable's CVE-2025-48757 (NVD 9.3, disputed by the supplier) describes the same class (NVD), and Escape's scan of roughly 5,600 vibe-coded apps reported 400-plus exposed secrets, with its findings concentrated in about 1.4K of them (Escape, October 29, 2025).

Self-hosted inference counts depend on method, so date them: 1,139 exposed Ollama instances in a 10-minute Shodan-seeded pull (Cisco Talos, September 1, 2025); 10.6K after honeypot filtering (Censys, September 24, 2025); 175,108 unique hosts over 293 days (SentinelLABS and Censys, January 29, 2026). What these assistants can honestly do for a small operator is in MyClaw, Microsoft, And The Honest Answer For Small Operators Who Want An AI Assistant Without Hiring An Employee.

(a) .gitignore done right, and the two traps

Git reads ignore patterns from three files: the shared .gitignore in the repo, the per-clone .git/info/exclude, and a per-user file named by core.excludesFile, which defaults to $XDG_CONFIG_HOME/git/ignore and falls back to $HOME/.config/git/ignore (git config docs). A leading ! negates a pattern, but you cannot re-include a file whose parent directory is excluded (gitignore manual). The patterns worth copying are in github/gitignore. Node's dotenv block is exactly three lines (Node.gitignore):

.env
.env.*
!.env.example

The negated line is the convention: .env.example is committed holding the variable names with blank or placeholder values, so a fresh clone knows what to set, and .env never is; dotenv's own README answers "Should I commit my .env file?" with a flat "No" (dotenv README). Terraform's template ignores *.tfstate, *.tfstate.*, *.tfvars and *.tfvars.json (Terraform.gitignore) because local state is a plaintext file that includes any secret values in your configuration (HashiCorp). Next.js's adds *.pem and .env*.local (Nextjs.gitignore); Cloudflare says add .dev.vars* and .env* (Cloudflare); Vite says add *.local (Vite); npm's CI docs put ${NPM_TOKEN} in .npmrc as a placeholder the CLI expands and say not to put a token in that file (npm docs). The template repository has no pattern for .netrc, id_rsa, credentials.json, service-account*.json or .npmrc (Nextjs.gitignore), so add those by hand, and put them in the per-user file so every clone on the machine gets them: unless XDG_CONFIG_HOME is set, that file is ~/.config/git/ignore, and creating it is the whole setup, no git config command needed (git config docs).

Trap one: ignore patterns do not touch files Git already tracks. The fix is git rm --cached .env (--cached removes only the index entry; the working-tree file is left alone), a commit, and then the .gitignore entry keeps it from coming back (gitignore manual). The commit that carried it still exists, which is section (d)'s problem: rotate anyway.

Trap two: agent config files carry tokens and are committed by design. Claude Code documents .claude/settings.json as the shared project file you commit so teammates get it, and its env block sets variables for every session and subprocess as plain text in the file; ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN are documented variables you can set there, and the reference steers API credentials to apiKeyHelper instead (Claude Code settings, settings reference, environment variables). The personal .claude/settings.local.json is excluded by design, but only when Claude Code itself writes it; a hand-made one gets no protection (Claude Code settings). The project MCP file .mcp.json is meant to be checked in, its headers and env fields accept literal secrets, and the safe form is ${VAR} expansion, as in "Authorization": "Bearer ${API_KEY}" (Claude Code MCP docs), and Cursor's .cursor/mcp.json does the same with ${env:NAME} (Cursor docs). github/gitignore's Global/Agents.gitignore ignores .claude/*.local.json and leaves .cursor/ and .claude/ as commented-out opt-ins because they are often intentionally committed; .cursor/mcp.json, .mcp.json and .claude/settings.json appear nowhere in it (Agents.gitignore). The GitHub code search API, queried September 11, 2026, returned 93,696 public .claude/settings.local.json files despite that file being gitignored by design (GitHub code search); a file count, not a validity count. A repo-controlled config file is also an attack surface. Cursor's CVE-2025-54135 let an indirect prompt injection create .cursor/mcp.json without approval when it did not already exist and trigger code execution, fixed in 1.3.9 per NVD (NVD); CVE-2026-21852 let a repository's settings file set ANTHROPIC_BASE_URL so Claude Code sent requests carrying the user's key before the trust prompt, fixed in 2.0.65 (advisory). My recommendation, not a sourced fact: review those three files like code, put every secret behind ${VAR}, and add a permissions.deny block with Read(./.env), Read(./.env.*) and Read(./secrets/**) so the agent cannot read the files you are keeping out of its transcript (settings reference). The wider problem of a repository steering your agent is Prompt injection when the data is the attack (Part 2 of 5).

(b) Filter before it lands: pre-commit scanners, push protection, the partner program

Gitleaks (MIT, 29,238 stars, v8.30.1 on March 21, 2026) ships 222 default rules (GitHub API). Install it (brew install gitleaks on macOS; binaries for other platforms are on the releases page) and, before wiring any hook, run gitleaks git -v inside each repository you already have: it walks the whole history through git log -p, and whatever it prints is section (d)'s worklist (gitleaks README). The hook itself needs the pre-commit framework: pip install pre-commit, then pre-commit install in each clone, and pre-commit run --all-files to check a tree once (pre-commit). The README's pre-commit block pins an older tag:

repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.24.2
    hooks:
      - id: gitleaks

followed by pre-commit autoupdate and pre-commit install (gitleaks README); autoupdate rewrites the rev to the current tag. The hook runs gitleaks git --pre-commit --redact --staged --verbose (.pre-commit-hooks.yaml), and SKIP=gitleaks in front of a commit skips it. TruffleHog (AGPL-3.0, v3.97.4 on September 3, 2026) verifies instead: it tests each candidate against the issuer's API, calling GetCallerIdentity for AWS, and reports verified, unverified or unknown; its README claims over 700 detectors (TruffleHog README). Its shipped hook line is:

trufflehog git file://. --since-commit HEAD --results=verified --fail --trust-local-git-config

but under the pre-commit framework, which sets PRE_COMMIT=1, TruffleHog's own code overrides the filter to verified,unknown, so an unverifiable hit also blocks the commit (PreCommit.md). The form PreCommit.md recommends is a local hook that relies on that detection, after brew install trufflehog:

repos:
  - repo: local
    hooks:
      - id: trufflehog
        name: TruffleHog
        description: Detect secrets in your data.
        entry: bash -c 'trufflehog git file://.'
        language: system
        stages: ["pre-commit", "pre-push"]

followed by pre-commit install (PreCommit.md). The trade for verification is that each candidate string is sent to its issuer's API to be tested, which is the price of fewer false positives. Every hook falls to git commit --no-verify, which Git's own documentation lists as the bypass (githooks).

GitHub push protection is the server-side layer, on by default for users since a rollout that began February 29, 2024 (changelog). It blocks secrets in command-line pushes, web commits, uploads, REST calls and, for public repos, GitHub MCP server interactions, but anyone with write access can bypass repository-level protection with one of three reasons (used in tests, false positive, fix it later), and a user-level bypass generates no alert unless repository-level protection is also on (push protection docs). Repository-level protection on a private repo requires GitHub Secret Protection at $19 per active committer per month (Advanced Security page), sold to Team-plan organizations since April 1, 2025 (changelog) on top of Team's $4 per user per month (GitHub pricing), and a personal Pro account cannot buy it (plans docs). For a two-person shop on a private repo that is $8 for Team plus $38 for Secret Protection, $46 a month; without it, nothing on GitHub's side scans a private push at all, and the pre-commit hook is the only filter (GitHub docs). GitLab's equivalent is Ultimate-only, off until enabled per project, and Ultimate is now custom-priced (GitLab docs). A gate that clears with one click deserves the treatment Green checks that cannot fail gives CI.

The partner program is what makes GitHub's clock different from every other platform's. For public repositories and public npm packages, a partner-pattern match makes GitHub POST the raw secret, its type and location to the provider, and GitHub tells partners to treat any secret it reports as public and compromised (partner program docs). The supported-patterns page listed 460 distinct provider patterns from 188 providers on September 11, 2026, 404 with push protection and 352 partner-reported (supported patterns). What the provider then does varies:

Provider On a public-repo match Source
GitHub's own OAuth, App and personal access tokens automatically revoked token revocation docs
Anthropic key automatically deactivated, user emailed (partner since August 20, 2024) Anthropic help center, GitHub changelog
OpenAI disabled immediately when detected on the public internet or in an app-store app, per OpenAI; no interval published OpenAI help center
AWS AWSCompromisedKeyQuarantineV3 attached (edited March 16, 2026; denies about 100 actions including bedrock:InvokeModel); the key still authenticates AWS policy reference
Google Cloud service-account keys auto-disabled only under the DISABLE_KEY org policy (default since June 16, 2024); detection not guaranteed Google Cloud blog, IAM docs
Google Gemini API Key; DeepSeek, Mistral, Cohere, Perplexity not partner patterns; the Gemini pattern also has no push protection supported patterns
Stripe monitors repos and forums itself; alerts the owner and may invalidate Stripe support
Slack legacy tester tokens in public repos revoked automatically Slack docs

Two consequences. AWS's quarantine is a policy, not a revocation: Truffle's 929 quarantined keys all still worked, and AWS's re:Post guidance still tells you to look for the V2 policy (AWS re:Post). And the Gemini gap is real money: Truffle found 2,863 live Google API keys in the November 2025 Common Crawl that could call Gemini, because enabling that API turned keys long documented as safe to embed into billable credentials (Truffle Security, February 25, 2026); a Google staff notice of June 18, 2026 says the Gemini API stops accepting requests from unrestricted keys starting June 19, 2026 (Google staff post).

(c) Secure deployment: where secrets live and what the publish directory ships

Secrets belong in the platform's write-only store, set from the CLI so they never sit in a file. Netlify's form is:

netlify env:set VAR_NAME value --context production --secret

where --secret makes the value unreadable afterward (Netlify CLI). Netlify then scans every file in the build, repo code and generated output alike, for the plaintext, base64 and URI-encoded forms of any value marked secret, and fails the build before publish if it finds one, writing the location to the deploy log; SECRETS_SCAN_ENABLED defaults to true, secret-marked values are barred from the post-processing scope, and the no-configuration "smart detection" is listed for Personal, Pro and Enterprise, not Free (Netlify Secrets Controller). Limiting a variable to the functions or runtime scope, so a key the build never needs never reaches the build, is Pro and Enterprise only (Netlify env docs). Cloudflare's is:

wrangler secret put <KEY>

invisible once set and deployed as a new version immediately; plaintext vars live in the committed Wrangler config, so nothing sensitive goes there (Cloudflare docs). Vercel relabeled its model on August 24, 2026 as Config (readable by members) versus Secret (unreadable after saving) (Vercel changelog); vercel env add defaults to the unreadable type for production and preview, its docs warn that piping the value in with echo leaves it in shell history, and vercel env run -- <command> puts the variables into one process without writing a file (Vercel CLI); its April 2026 incident, in which an attacker who came in through an employee's compromised third-party AI tool decrypted non-sensitive variables, is the argument for the Secret flag (Vercel bulletin). GitHub Actions secrets are set with gh secret set SECRET_NAME, withheld from workflows triggered by fork pull requests, and redacted from logs with the caveat that "this redaction is not guaranteed" (GitHub docs). For the local copy, the one that ends up in a .env on a laptop, a secrets manager with a CLI keeps references in the file and values out of it: 1Password's op run --env-file="./prod.env" -- <command> resolves op://vault/item/field references into the environment of that one process, so the file you might commit holds references, not values, and rotating the vault entry rotates that copy with it; 1Password listed Individual at $2.99 a month (promotional, $3.99 regular) and a Teams Starter Pack at $24.95 a month for 10 members on September 11, 2026 (1Password pricing). Doppler is free for 3 users, then $8 a month per additional user (Doppler pricing); Infisical is free for 5 identities and $20 per identity per month billed annually above that (Infisical pricing). Better than a stored cloud key is none: declare permissions: id-token: write and let aws-actions/configure-aws-credentials assume a role against an identity provider at https://token.actions.githubusercontent.com with audience sts.amazonaws.com; repositories created after July 15, 2026 use an immutable subject format with owner and repo IDs (GitHub OIDC for AWS).

Then there is what the publish directory ships. Netlify deploys only files inside the publish directory (Netlify build docs) and tells you to keep the functions directory outside it so source is not deployed as part of the site (Netlify functions docs). Its deploy tooling drops dot-prefixed files (except .well-known) and the _redirects and _headers config files, and uploads everything else, underscore-prefixed scripts included (that is the CLI's upload filter rather than anything the docs promise; the source line is in the notes below). I learned that firsthand. On a handful of small sites I maintain, the publish directory was . because the sites are hand-built HTML with no build step, and one day I found that internal _patch_*.mjs files and page-generation scripts were live URLs on four of them. A stray .env in that root would not have been served; a stray _patch_*.mjs was. No credentials were in those scripts because every key they used came from environment variables, and that is the point: the mistake was embarrassing, not expensive, because the secret lived where the tree could not reach. Cloudflare's answer to the same problem is a .assetsignore file in the assets root, same format as .gitignore, with _worker.js, _redirects and _headers as its documented example (Cloudflare docs). GitHub Pages has the same shape in reverse: the site is public even when the repository is private (GitHub Pages docs), and its default Jekyll build skips files and folders that start with _, . or #, which is the filter a repo gives up when it adds .nojekyll to publish raw static files (GitHub Pages and Jekyll).

Logs, bundles and source maps are the other doors. Netlify's default for a site linked to a public repo is public deploy logs, readable by anyone with a deploy detail URL; the fix is one setting, Private logs, which limits them to site members (Netlify build docs). Vercel's /_src and /_logs paths require login, and the old vercel --public route is gone: the public property is no longer accepted (Vercel vercel.json reference). Vercel's build logs redact a Secret-type value only when it is 32 characters or longer, so a short token printed by a build step appears in full (Vercel docs). Bundles are the rule everybody breaks once: any NEXT_PUBLIC_ variable is inlined into the client JavaScript at build time (Next.js docs), and VITE_ variables are bundled into source and the docs say they must not hold API keys (Vite). The keys designed for that bundle are Stripe's pk_ publishable keys and nothing else of Stripe's (Stripe docs), and a Google Maps key with an HTTP-referrer restriction, since Google says you are financially responsible for abuse of unrestricted keys (Google Maps docs). Source maps are the third door. Next.js disables browser source maps in production builds unless productionBrowserSourceMaps: true is set, and its docs give the reason as preventing you leaking your source on the client (Next.js docs); Vercel's Protected Source Maps answers a .map request from anyone not logged in with a 404, is on by default for new projects but opt-in for existing ones, and with it off the deployment serves source maps to anyone who asks (Vercel docs). The client-side half of this problem has its own post, Your API keys are in your HTML and anyone can read them.

The supply chain reaches your CI secrets from the other direction. On the afternoon of March 14, 2025 UTC, tags v1 through v45.0.7 of tj-actions/changed-files, used by more than 23,000 repositories, were repointed at a commit that dumped the runner's memory, base64-encoded the secrets and wrote them into build logs, readable by anyone on public repos; CISA added CVE-2025-30066 to its Known Exploited Vulnerabilities catalog on March 18, 2025 (GitHub advisory, StepSecurity, NVD). GitHub says pinning an action to a full-length commit SHA is currently the only way to use it as an immutable release (GitHub docs). Shai-Hulud began September 14, 2025, ran TruffleHog on infected machines to find secrets, published itself to every package a stolen npm token could reach, and led GitHub to remove roughly 500 packages (GitHub, Wiz); npm's response gave write-enabled granular tokens a 7-day default and 90-day maximum (GitHub changelog, September 29, 2025) and revoked every classic token on December 9, 2025 (GitHub changelog, December 9, 2025). The headers and publish-directory checklist is Static-Site Generator Deployment Hardening; the hook that would have caught my _patch_*.mjs files is The Pre-Deploy Hook That Kills Regressions Before Netlify Ships Them.

(d) Rotate all of them: revoke first, rewrite second, audit third, then the inventory

GitHub's guide puts the order in one sentence: if the leaked data is a secret, as a first step you revoke and/or rotate it, and once that is done a history rewrite may not be warranted (GitHub docs). Its remediation tutorial runs nine steps, revoke at step 4 and clean the repository at step 7, and states that removing the secret, pushing a new commit, or deleting and recreating the repository does not prevent exploitation; if downtime matters, generate the new secret and cut over before revoking the old one (GitHub docs). The reason is the fork network: commits pushed to any repository in a network can be reached from any other, and when a public repo is deleted an active fork becomes the new upstream (GitHub forks docs). Truffle showed a dangling commit stays reachable by SHA and that GitHub accepts four-character prefixes (65,536 values); when it reported a deleted-but-forked private key to a large company, GitHub's disclosure program called the behavior an intentional design decision, and Truffle's conclusion was that rotation is the only real remediation (Truffle Security).

Rewrite second, with the tool GitHub documents. You need git-filter-repo 2.47 or later for --sensitive-data-removal (git-filter-repo); the commands, quoted from GitHub's page, are:

git-filter-repo --sensitive-data-removal --invert-paths --path PATH-TO-YOUR-FILE-WITH-SENSITIVE-DATA
git-filter-repo --sensitive-data-removal --replace-text ../passwords.txt
git push --force --mirror origin

run in a fresh clone, irreversible once pushed (GitHub docs). git filter-branch's own manual says its use is not recommended and points to filter-repo (git-filter-branch manual). The rewrite recontaminates the moment a colleague with an old clone runs git pull then git push, and GitHub Support purges cached views and pull-request references only where it judges rotation cannot mitigate the risk and no fork still references the data (GitHub docs).

Audit third. For AWS: generate an IAM credential report, review CloudTrail for created access keys, policies, roles and temporary credentials, confirm the quarantine policy is attached, and answer the support case (AWS re:Post); if Bedrock is enabled, search CloudTrail for DeleteModelInvocationLoggingConfiguration and PutFoundationModelEntitlement as well (Sysdig). For an OpenAI key, per-key usage is on the Usage page once tracking is on, which is the place to look (OpenAI). Stripe wants you in the key's Workbench request logs looking for unrecognized activity and payouts (Stripe support); GitHub says to compare ssh-add -l -E sha256 fingerprints against your account's keys and delete unknown ones (GitHub docs).

howtorotate.com, a Truffle-sponsored collection, uses three steps for every provider: generate the new key, replace the compromised key everywhere it is used, revoke the compromised key (howtorotate). The middle step is the whole difficulty.

Secret Rotation mechanics Lifetime controls Docs
AWS access key aws iam create-access-key, update consumers, check aws iam get-access-key-last-used, set Inactive with aws iam update-access-key, then aws iam delete-access-key prefer roles; STS sessions run 15 minutes to 12 hours AWS, AWS, STS
OpenAI rotate from the API Keys page; keys carry expiration dates; admins can enforce a maximum lifetime; IP allowlisting is available spend limit is soft until "Enforce a hard limit" is on, then 429 project_spend_limit_exceeded OpenAI, OpenAI
Anthropic Disable is reversible, Delete is permanent; the Admin API deactivates with POST /v1/organizations/api_keys/{id} and {"status": "inactive"} presets of 3 hours, 1 day, 7 days, 30 days or Never; archiving a workspace kills its keys; WIF means no key Anthropic, workspaces, Admin API
Google Cloud service account delete the key in IAM immediately; source control keeps a permanent record no expiry by default; prefer Workload Identity Federation; set DISABLE_KEY Google Cloud
Stripe Rotate key in the Dashboard, up to 7 days overlap or "Now"; check request logs before expiring the old one restricted rk_ keys over sk_; IP and ASN access policies; webhook secrets are separate Stripe
GitHub tokens delete at Settings > Developer settings > Personal access tokens; fine-grained PATs expire in 1 to 366 days; classic tokens can be infinite; deploy keys never expire and stay active after their creator leaves org maximum-lifetime policy GitHub, deploy keys
Netlify personal access token with an expiration date, shown once, used as NETLIFY_AUTH_TOKEN; revoked from the same list via Options > Delete personal token expiration selector at creation Netlify
Cloudflare Roll the API token (same permissions, old secret invalid); the Global API Key is legacy TTL and client IP filtering Cloudflare
Slack auth.revoke; opt-in rotation expires access tokens every 12 hours and cannot be turned off minimum scopes Slack
Postgres ALTER ROLE name WITH PASSWORD (prefer psql's \password so the cleartext is not logged), then pg_terminate_backend(pid) none built in PostgreSQL
JWT signing secret HMAC secret at least the hash size (256 bits for HS256) with 160 bits of entropy; deny list keyed on jti until exp; rotate, then revoke short expiry OWASP, Auth0

Every row of that table is easy. The hard part is knowing where the old value lives. An automation agent I ran once printed a bearer token into its own transcript. When I mapped out the rotation, that one token turned out to exist in five places: a Worker secret, a second platform's environment variable, a hosting environment variable, a CI secret and a local file, with no atomic cutover between them; whichever copy changed first, something would run on the old value until the last one changed. The lesson is the map. OpenClaw's five .bak config copies are the same shape on one machine. Cloudflare's February 2024 post-mortem is the same lesson at scale: after the October 2023 Okta compromise it rotated thousands of credentials and missed one service token and three service accounts, which the attacker used to reach 120 of its 11,904 code repositories; the fix rotated more than 5,000 production credentials (Cloudflare). And on March 19, 2026, 75 of 76 tags of the Trivy GitHub Action were force-pushed to malicious commits through a credential that survived because, in the maintainers' words, an earlier rotation "was not fully atomic" (Socket). So the inventory is the deliverable: one row per secret with an owner, every place a copy lives, an expiry, and short-lived credentials by default (OIDC roles, STS sessions, npm's seven-day tokens) so most rows expire themselves. Two of those columns can be filled by tooling rather than memory: trufflehog filesystem <paths> pointed at a home directory finds the local files, and trufflehog github --org=<org> --results=verified reports which copies in your repositories still authenticate (TruffleHog README). The platform copies you list by hand: every netlify env:set, wrangler secret put and gh secret set you have ever run is a row, which is the argument for writing the list the first time rather than reconstructing it during an incident.

Billing alarms are the last line and they lag. AWS Budgets updates up to three times a day, typically 8 to 12 hours apart, and AWS warns you may exceed a threshold before it can notify you (AWS Budgets); the CloudWatch billing alarm fires only on actual charges, exists only after you enable Receive CloudWatch Billing Alerts in Billing Preferences, and lives in US East (N. Virginia), the only Region that holds the billing metric (CloudWatch docs). The one AWS control that acts instead of emailing is a budget action, which can attach a custom IAM policy that denies provisioning when a cost budget trips (AWS Budgets); and the smallest operators have a blunter option, since an account still on the Free Tier plan is not charged unless it converts to a paid plan (AWS Free Tier). OpenAI's spend alert is a notification; traffic stops only when a hard spend limit is set (OpenAI docs). That hard limit is a real stop, but enforcement is not instantaneous (OpenAI). Anthropic's version is per workspace: Settings > Workspaces > Limits sets a spend limit lower than the organization's, with an email at a chosen amount, and it cannot be set on the Default Workspace (Anthropic help); keys can be scoped to one workspace and archiving a workspace archives every key in it within seconds, which makes one workspace per project the cleanest blast-radius boundary Anthropic offers (Anthropic workspaces). Two legal clocks run alongside: GDPR Article 33's 72 hours where feasible (GDPR Art. 33) and breach-notification laws in all 50 US states (NCSL). Spend caps for agents specifically are in How a Small Business Runs AI Agents Without a $47,000 Surprise Bill. If you are building and deploying your own small-business site and want the whole map, from publish directory to environment variables to the rotation list, that is the book I wrote for it: The $97 Launch.

Related reading

Fact-check notes and sources

  • Orca 2 minutes: Orca Security's 2023 Honeypotting in the Cloud report (research January to May 2023, published June 20, 2023): GitHub within 2 minutes, S3 after 8 hours, ECR after 4 months, orca.security. Vendor self-measurement.
  • Unit 42 five minutes, 13:30:22 and 13:34:15: Palo Alto Unit 42, October 30, 2023, unit42.paloaltonetworks.com. The actor also blocklisted accounts that repeatedly exposed keys.
  • Clutch 6.6 minutes, 1 minute fastest, 1.4-minute alert: Clutch Security Part 1, December 3, 2024; the 6.6 average is over the 10 exploited GitHub scenarios, clutch.security. Parts 2, 3 and 5 supply the Docker Hub, PyPI, npm, Pastebin and RDS rows; Docker Hub scenarios used canary tokens, so no AWS alert was possible.
  • Cybenari 127 seconds: the article's own Fastest Access Time chart (GitHub 127 s, PyPI 119 s, Pastebin 50 min, web server 47 h, Docker Hub 6.9 days), as tabulated by ThreatDown on September 4, 2024, cybenari.com, threatdown.com. The prose says "within seconds"; the chart is used. The npm figure is contested by Clutch Part 2.
  • Comparitech under one minute: first published October 1, 2020, updated July 10, 2022, comparitech.com. Treated as a 2020 experiment.
  • Permiso within minutes, 75,000 invocations, 42 days: Permiso, October 3, 2024, permiso.io. Quarantine V2 on August 1, 2024 blocked no Bedrock actions; five bedrock: actions added October 2, 2024.
  • 81% never removed, 19% after 16 days: NDSS 2019 paper on secret leakage on GitHub, ndss-symposium.org. Its separate 20-second median is GitHub Search API indexing latency in 2017 to 2018, not attacker use, and is not cited as an abuse time.
  • GitGuardian counts and persistence: 12.8M (2023) and over 90% active after five days from the 2024 report; 23.8M (2024) from the 2025 report; 28.65M, 1.94B commits, 1.5% baseline, 1,275,105 AI secrets, 113,000 DeepSeek keys, 24,008 MCP-config secrets with 2,117 valid, 3.2% vs 1.5% and above 64% still valid in January 2026 from the 2026 report, blog.gitguardian.com. Vendor self-reported; the report page's restated series does not match the year-by-year editions, so each year is cited to the edition that published it. The 19.2% and 14% shares are of valid secrets.
  • "Exploited in seconds": GitHub's April 1, 2025 post carries no citation for it; the remediation tutorial on the same site says located in minutes, exploited within hours; the measured GitHub clock is one to seven minutes, github.blog.
  • GitHub 39 million and 19 million: the 39 million is sourced by GitHub's April 1, 2025 post to Octoverse 2024, whose window is October 1, 2023 to September 30, 2024 and which is not split by repository visibility, github.blog. The April 14, 2026 post's exact wording is that in 2025 "customers using Secret Protection scanned nearly 2 billion pushes and blocked 19 million secret exposures," so it counts blocks, not leaks, github.blog.
  • HoneyMCP: Asteros released the MCP-server honeypot on May 6, 2026 with a leaked-key mode and no measured results; the repository's last push is 2026-05-06 per the GitHub API on September 11, 2026, api.github.com.
  • Truffle AWS-key figures: launch post August 20, 2026 and research post August 19, 2026; 88% is of 10,616 re-verified keys, the median and 13.7% rotation rate of 2,903, the 84% admin share of 1,157 users, trufflesecurity.com. The 64,024 headline denominator is not used.
  • Deleted forks, 40 keys, 65,536 prefixes: Truffle Security, July 24, 2024, trufflesecurity.com; GitHub's fork behavior confirmed in its own docs, docs.github.com.
  • Sysdig $46,080 and $100,000 per day: modeled worst cases, not observed bills, May 6 and September 18, 2024, sysdig.com. The $49,595.83 in 4.5 days is Sysdig's recomputation across one instance's pool of 36 keys from at least 13 organizations, February 7, 2025, sysdig.com; $30 per 30-day token is one storefront's price.
  • Microsoft's Storm-2139 case: 1:24-cv-2323 (E.D. Va.); complaint sealed December 19, 2024; the amended complaint of February 28, 2025 says the manner of obtaining all keys is unknown; default judgment and permanent injunction entered July 9, 2026 (Dkt. 72) with Microsoft forgoing damages, courtlistener.com. Defendants are not named here.
  • Pillar and Anthropic: Pillar's 35,000 sessions, 972 a day and 2 to 8 hour lag, January 28, 2026, pillar.security; Anthropic's September 2026 statements about stolen keys are per actor, not report-wide, anthropic.com.
  • Agent-surface scanning: GreyNoise reports days to discovery and no exploitation of its MCP honeypots, including the one with an exposed key, and does not itself say the key went unused, greynoise.io; the 49-source MCP handshake count and the mcp.json probes are one small host's 14-day logs, isc.sans.edu; the Canarytokens catalog lists no OpenAI, Anthropic or Google AI key type, canarytokens.org, which is consistent with, but not stated by Thinkst as the reason for, the AWS-only timing data. The three-clock summary is my reading of those studies, not a figure any one of them states.
  • Small-victim bills: all self-reported forum or press accounts told by role: WP Tavern, September 26, 2014, with the developer's own comment on the waiver, wptavern.com; The Register, March 3, 2026 ($82,314.44, outcome unknown), theregister.com; the Google forum threads (54,000-plus euros denied; the family shop's timeline, whose key sat in public site HTML since 2021, became Gemini-capable on December 27, 2025 and took 515,226 calls in the first hour on June 10, 2026, per the owner's own audit-log summary). No vendor page promises a waiver: OpenAI's key-safety article lists unexpected charges as a consequence, and Anthropic's compromised-key article (updated March 16, 2026) says revoke, create a new key in a secrets manager, contact Support, and nothing about refunds, support.claude.com.
  • OpenClaw and Ollama counts: STRIKE's 42.9K (February 9, 2026) and The Register's 135,000 the same day are snapshots from a dashboard updating every 15 minutes; Censys's 21,639 is as of January 31, 2026. Ollama: 1,139 (Talos, September 1, 2025), 10.6K (Censys, September 24, 2025), 175,108 (SentinelLABS and Censys, January 29, 2026), each a different method.
  • OpenClaw CVE-2026-25253 and the .bak copies: CVSS 8.8, versions before 2026.1.29, depthfirst.com. OX Security's January 29, 2026 post says the software (then named MoltBot) stores configuration under ~/.clawdbot in cleartext and cycles through five .bak.X backups of the main config, so a key deleted from the UI can remain in one, ox.security.
  • Moltbook: Wiz, February 2, 2026, disclosure timeline January 31 21:48 UTC to February 1 01:00 UTC, wiz.io. Supabase's publishable key is documented as safe to expose; the failure was missing RLS.
  • Lovable and Escape: CVE-2025-48757 is 9.3 on NVD with status Deferred and tagged disputed, nvd.nist.gov; the researcher's own page scores it 8.26, and the April 24, 2025 patch was a scanner feature that did not address the RLS flaw. Escape's October 29, 2025 post says it analyzed over 5,600 apps and found 400-plus exposed secrets; its report page attributes the findings to about 1,400 of them, so both denominators are given, escape.tech.
  • Agent config files: Claude Code and Cursor docs read September 11, 2026; the 93,696 figure is a file count from the legacy GitHub search API, which indexes default branches of recently active repositories only. The settings page says Claude Code adds **/.claude/settings.local.json to the global git excludes file the first time it writes the file in a repository that does not already ignore it, and that a file you create by hand must be gitignored by you. CVE-2026-21852 was published January 20, 2026 (NVD January 21) and patched in 2.0.65. Cursor's CVE-2025-54135 is cited to NVD's description (fixed in 1.3.9); the vendor advisory gives a narrower vulnerable range and a different score, so "per NVD" is stated, nvd.nist.gov. The settings-file review pattern and the permissions.deny block are my recommendation. The absence of .netrc, id_rsa, credentials.json, service-account and npmrc patterns is a code search of the github/gitignore repository, cited to its Next.js template for want of a repo-level URL.
  • gitleaks and TruffleHog: star counts, releases and rule counts observed September 11, 2026 via the GitHub API; the README's rev: v8.24.2 is quoted as it stands and pre-commit autoupdate rewrites it; TruffleHog's README says over 700 detectors in one section and over 800 secret types in another, so only the lower figure is used; its effective pre-commit filter of verified,unknown comes from its main.go override when PRE_COMMIT=1 is set, per PreCommit.md and the source.
  • Push protection pricing: $19 per active committer per month, github.com; Team at $4 per user per month on the pricing page September 11, 2026, github.com/pricing; a committer is active if a commit of theirs was pushed in the last 90 days.
  • Partner revocation: OpenAI's "disabled immediately" statement is from its own help center; Anthropic's automatic deactivation is from its help article updated March 16, 2026; AWS quarantine V3 was edited March 16, 2026 while re:Post still references V2; Google's auto-disable covers service-account keys under the DISABLE_KEY constraint and Google does not guarantee detection.
  • Platform secret stores: Netlify's --secret flag and Cloudflare's wrangler secret put are quoted from their CLI docs as read September 11, 2026; GitHub's Actions secrets page says secrets are set with gh secret set, are not passed to runners for workflows from forked repositories (except GITHUB_TOKEN), and that log redaction "is not guaranteed" because values can be transformed, docs.github.com.
  • Netlify publish directory and Cloudflare .assetsignore: the Netlify docs say only the publish directory is deployed and say nothing about dotfiles; the dotfile-skip and underscore-script behavior is the CLI's own upload filter, which skips any name starting with a dot except .well-known, treats _redirects and _headers as config, and uploads everything else, src/commands/deploy/deploy.ts in netlify/cli (read on 2026-09-11); it matches what I saw on my own sites. Cloudflare's static-assets binding page says a .assetsignore in the assets root takes the same format as .gitignore and gives _worker.js, _redirects and _headers as the example, developers.cloudflare.com.
  • Vercel: vercel --public and the public property were removed in June 2026; the Config/Secret model is from the August 24, 2026 changelog; the April 2026 bulletin does not itself say sensitive-marked variables were untouched.
  • tj-actions, Shai-Hulud, npm tokens, Trivy: StepSecurity puts the compromise at about 16:00 UTC March 14, 2025 and Unit 42's timeline puts the tag-replacing push at 16:57 UTC, so "afternoon of March 14, 2025 UTC" is used, stepsecurity.io; NVD's description says tags v1 through v45.0.7 were modified on March 14 and 15, 2025, and its CISA fields give a KEV date of March 18, 2025, nvd.nist.gov. Shai-Hulud's earliest malicious version is timestamped September 14, 2025 with the popular-package wave on September 15, and package counts are GitHub's 500+ versus Socket's nearly 500, wiz.io. The npm token changes are from GitHub's September 29, 2025 changelog (7-day default, 90-day maximum for write-enabled granular tokens); that post scheduled classic-token revocation for mid-November 2025, and the December 9, 2025 changelog is where GitHub says all classic tokens were permanently revoked, so the later date is used. Trivy's 75 of 76 tags and the maintainers' "not fully atomic" wording per Socket, socket.dev.
  • Cloudflare 2023 incident: alert at 16:00 UTC November 23, 2023, Smartsheet account deactivated 35 minutes later, all access terminated November 24, blog.cloudflare.com.
  • Rotation runbook: each row cites the provider's own documentation as read September 11, 2026. Anthropic's Admin API accepts three credential types and updates keys with POST, not PATCH; OpenAI's project spend limit is soft until "Enforce a hard limit" is toggled.
  • Billing alarm lag: AWS Budgets updates up to three times a day, 8 to 12 hours apart; CloudWatch billing alarms trigger on actual charges only; OpenAI's production guide says spend alerts notify at a dollar threshold and "Hard spend limits stop affected API traffic when tracked spend reaches the limit," developers.openai.com.
  • Netlify Secrets Controller and scopes: scanning of all build files for plaintext, base64 and URI-encoded secret values, build failure before publish, SECRETS_SCAN_ENABLED default true, the post-processing exclusion, and smart detection listed for Personal, Pro and Enterprise, from the Secrets Controller page as read September 11, 2026, docs.netlify.com; scope limiting is Pro and Enterprise per the environment-variables overview, docs.netlify.com; Private logs is the setting named on the build overview page.
  • Vercel CLI, redaction and source maps: vercel env add defaulting to sensitive and the echo warning from the CLI env page, vercel.com; the 32-character redaction floor from the sensitive-variables page, vercel.com; Protected Source Maps (404 to unauthenticated requests, default on for new projects, opt-in for existing) from its docs page last updated August 11, 2026, vercel.com; Next.js production source maps off unless productionBrowserSourceMaps is set, nextjs.org.
  • GitHub Pages: public even when the repository is private, docs.github.com; the Jekyll build's skip list for _, . and # prefixes is on the Jekyll page, and "gives up that filter" is my inference from that list plus GitHub's statement that a non-Jekyll site publishes the static files you push.
  • Scanner install and history-scan commands: brew install gitleaks, gitleaks git -v and the git-log-p mechanism from the gitleaks README; pip install pre-commit, pre-commit install and pre-commit run --all-files from pre-commit.com; brew install trufflehog, the repo: local block and pre-commit install quoted from TruffleHog's PreCommit.md; trufflehog filesystem <paths> and trufflehog github --org=<org> --results=verified from the TruffleHog README, all as read September 11, 2026. That verification sends each candidate to the issuer's API is the README's own description of how verification works.
  • $46 a month and GitLab: two active committers at $19 plus two Team seats at $4; GitLab secret push protection is Ultimate-tier, off by default per project, and GitLab's pricing page showed Ultimate as custom pricing on September 11, 2026, docs.gitlab.com.
  • Bedrock logging and CloudTrail names: Permiso says none of its client victims had invocation logging enabled; Sysdig's September 18, 2024 update reports DeleteModelInvocationLoggingConfiguration and PutFoundationModelEntitlement calls and notes CloudTrail still records the call, sysdig.com. OpenAI's Usage page statement is from its production best-practices guide.
  • Billing alarm prerequisites, budget actions, Free Tier, Anthropic workspaces: the Receive CloudWatch Billing Alerts preference and the US East (N. Virginia) metric location from the CloudWatch page; budget actions applying a custom IAM policy from the AWS Budgets page; "You won't be charged unless you convert to a Paid plan" from the AWS Free Tier page as read September 2026, aws.amazon.com; Anthropic workspace limits (lower than the organization limit, email at a chosen amount, not on the Default Workspace) from the compromised-key help article, and the archive-within-seconds behavior from the workspaces doc, platform.claude.com.
  • Secrets-manager prices: 1Password Individual $2.99 promotional ($3.99 regular), Teams Starter Pack $24.95 a month for 10 members, and the op run --env-file form, from 1Password's pricing pages on September 11, 2026; Doppler free for 3 users then $8 per additional user; Infisical free for 5 identities then $20 per identity per month billed annually ($23 monthly). All list prices on the day, subject to change.
  • My own incidents: the stray _patch_*.mjs files and the agent transcript token are first-person accounts of a handful of small sites I maintain and an automation agent I ran; no platform, client or product is named, and nothing here claims the rotation was completed or not.

This post is informational, not legal, financial, or security advice. Mentions of third parties are nominative fair use. No affiliation is implied.

← Back to Blog

Accessibility Options

Text Size
High Contrast
Reduce Motion
Reading Guide
Link Highlighting
Accessibility Statement

J.A. Watte is committed to ensuring digital accessibility for people with disabilities. This site conforms to WCAG 2.1 and 2.2 Level AA guidelines.

Measures Taken

  • Semantic HTML with proper heading hierarchy
  • ARIA labels and roles for interactive components
  • Color contrast ratios meeting WCAG AA (4.5:1)
  • Full keyboard navigation support
  • Skip navigation link
  • Visible focus indicators (3:1 contrast)
  • 44px minimum touch/click targets
  • Dark/light theme with system preference detection
  • Responsive design for all devices
  • Reduced motion support (CSS + toggle)
  • Text size customization (14px–20px)
  • Print stylesheet

Feedback

Contact: jwatte.com/contact

Full Accessibility StatementPrivacy Policy

Last updated: April 2026