[{"data":1,"prerenderedAt":1439},["ShallowReactive",2],{"blog-how-to/fix-anthropic-api-key-exposure":3},{"id":4,"title":5,"body":6,"category":1408,"date":1409,"dateModified":1409,"description":1410,"draft":1411,"extension":1412,"faq":1413,"featured":1411,"headerVariant":1424,"image":1425,"keywords":1426,"meta":1427,"navigation":277,"ogDescription":1428,"ogTitle":1425,"path":1429,"readTime":1430,"schemaOrg":1431,"schemaType":1432,"seo":1433,"sitemap":1434,"stem":1435,"tags":1436,"twitterCard":1437,"__hash__":1438},"blog/blog/how-to/fix-anthropic-api-key-exposure.md","How to Fix Anthropic API Key Exposure (2026)",{"type":7,"value":8,"toc":1396},"minimark",[9,18,21,42,47,50,69,82,88,94,103,107,110,115,122,230,233,237,240,312,315,319,322,413,422,426,444,477,594,963,974,1142,1146,1149,1162,1165,1212,1215,1221,1231,1235,1238,1241,1262,1269,1272,1278,1284,1287,1303,1307,1362,1381,1392],[10,11,12,13,17],"p",{},"If you created an Anthropic API key before July 8, 2026, it never expires. Anthropic added optional expiration dates on that date, and the change explicitly did not touch existing keys. So a ",[14,15,16],"code",{},"sk-ant-"," key that got pasted into a repo, a config file, or a deploy log months ago is still accepting requests today, and it will keep accepting them until a human deletes it.",[10,19,20],{},"That is the part founders get wrong. People assume a forgotten credential eventually dies on its own. It doesn't. Anthropic keys have no built-in shelf life unless you opted into one, and the opt-in only became possible three weeks ago.",[22,23,24],"tldr",{},[10,25,26,27,29,30,33,34,37,38,41],{},"Anthropic keys start with ",[14,28,16],{}," (admin keys use ",[14,31,32],{},"sk-ant-admin",") and, unless you set an expiration after July 8, 2026, they never expire. If yours leaked: revoke it in the Anthropic Console first, then scrub git history, then move the key into a server-side environment variable with no ",[14,35,36],{},"NEXT_PUBLIC_"," or ",[14,39,40],{},"VITE_"," prefix. When you create the replacement, set an expiry instead of leaving it on Never.",[43,44,46],"h2",{"id":45},"where-anthropic-keys-actually-leak","Where Anthropic Keys Actually Leak",[10,48,49],{},"The Anthropic key leaks we see on scanned apps are not usually a dramatic breach. They're one of four boring mistakes.",[10,51,52,56,57,60,61,64,65,68],{},[53,54,55],"strong",{},"Agent and MCP config files committed to the repo."," A ",[14,58,59],{},".claude/"," directory or an MCP server config with the key sitting in an ",[14,62,63],{},"env"," block. These files feel like local tooling, so they don't get the same scrutiny as source code, and they end up in the first ",[14,66,67],{},"git add .",".",[10,70,71,74,75,37,78,81],{},[53,72,73],{},"A public prefix on the variable name."," ",[14,76,77],{},"NEXT_PUBLIC_ANTHROPIC_API_KEY",[14,79,80],{},"VITE_ANTHROPIC_API_KEY",". Both prefixes exist specifically to copy a value into the browser bundle. The name is doing exactly what it says, just not what you wanted.",[10,83,84,87],{},[53,85,86],{},"Keys pasted into agent context that gets logged."," You paste a key into a prompt so the assistant can \"test the call.\" That prompt now lives in a session transcript, a log file, or a debugging artifact you forget about.",[10,89,90,93],{},[53,91,92],{},"Preview deploys from Lovable, Bolt, and v0."," The preview URL is public, the build includes whatever env vars the platform bundled, and nobody remembers to clean up the throwaway project.",[95,96,97],"danger-box",{},[10,98,99,100,102],{},"A ",[14,101,32],{}," key is not the same risk class as a normal API key. Admin keys talk to the Admin API, which manages organization-level resources including the API keys themselves. If an admin key leaked, treat it as an account-level incident, not a billing one.",[43,104,106],{"id":105},"how-to-tell-if-your-key-leaked","How to Tell If Your Key Leaked",[10,108,109],{},"Three checks. Do all of them, because a key can be in your bundle and your history and your config, and fixing one place leaves the other two live.",[111,112,114],"h3",{"id":113},"check-1-your-deployed-javascript-bundle","Check 1: Your deployed JavaScript bundle",[10,116,117,118,121],{},"Open your live site, press F12, go to Sources, and search across all files for ",[14,119,120],{},"sk-ant",". You can also do it from the terminal without a browser:",[123,124,129],"pre",{"className":125,"code":126,"language":127,"meta":128,"style":128},"language-bash shiki shiki-themes github-dark","# Pull your deployed JS and grep it\ncurl -s https://yourapp.com | grep -oE '/[^\"]+\\.js' | sort -u | \\\n  while read f; do curl -s \"https://yourapp.com$f\" | grep -o 'sk-ant-[A-Za-z0-9_-]*' ; done\n","bash","",[14,130,131,140,181],{"__ignoreMap":128},[132,133,136],"span",{"class":134,"line":135},"line",1,[132,137,139],{"class":138},"sAwPA","# Pull your deployed JS and grep it\n",[132,141,143,147,151,155,159,162,165,168,170,173,176,178],{"class":134,"line":142},2,[132,144,146],{"class":145},"svObZ","curl",[132,148,150],{"class":149},"sDLfK"," -s",[132,152,154],{"class":153},"sU2Wk"," https://yourapp.com",[132,156,158],{"class":157},"snl16"," |",[132,160,161],{"class":145}," grep",[132,163,164],{"class":149}," -oE",[132,166,167],{"class":153}," '/[^\"]+\\.js'",[132,169,158],{"class":157},[132,171,172],{"class":145}," sort",[132,174,175],{"class":149}," -u",[132,177,158],{"class":157},[132,179,180],{"class":149}," \\\n",[132,182,184,187,190,193,197,200,203,205,208,211,214,216,218,221,224,227],{"class":134,"line":183},3,[132,185,186],{"class":157},"  while",[132,188,189],{"class":153}," read",[132,191,192],{"class":153}," f",[132,194,196],{"class":195},"s95oV","; ",[132,198,199],{"class":157},"do",[132,201,202],{"class":145}," curl",[132,204,150],{"class":149},[132,206,207],{"class":153}," \"https://yourapp.com",[132,209,210],{"class":195},"$f",[132,212,213],{"class":153},"\"",[132,215,158],{"class":157},[132,217,161],{"class":145},[132,219,220],{"class":149}," -o",[132,222,223],{"class":153}," 'sk-ant-[A-Za-z0-9_-]*'",[132,225,226],{"class":195}," ; ",[132,228,229],{"class":157},"done\n",[10,231,232],{},"Any hit means the key is readable by every visitor. There is no partial version of this problem.",[111,234,236],{"id":235},"check-2-your-git-history","Check 2: Your git history",[10,238,239],{},"Deleting the file does not remove the key. Git keeps every version of every file forever.",[123,241,243],{"className":125,"code":242,"language":127,"meta":128,"style":128},"# Every commit that ever contained an Anthropic key\ngit log --all -p -S 'sk-ant-' --oneline\n\n# Config files people forget are tracked\ngit log --all --full-history -- .env .env.local .claude/ .mcp.json\n",[14,244,245,250,273,279,285],{"__ignoreMap":128},[132,246,247],{"class":134,"line":135},[132,248,249],{"class":138},"# Every commit that ever contained an Anthropic key\n",[132,251,252,255,258,261,264,267,270],{"class":134,"line":142},[132,253,254],{"class":145},"git",[132,256,257],{"class":153}," log",[132,259,260],{"class":149}," --all",[132,262,263],{"class":149}," -p",[132,265,266],{"class":149}," -S",[132,268,269],{"class":153}," 'sk-ant-'",[132,271,272],{"class":149}," --oneline\n",[132,274,275],{"class":134,"line":183},[132,276,278],{"emptyLinePlaceholder":277},true,"\n",[132,280,282],{"class":134,"line":281},4,[132,283,284],{"class":138},"# Config files people forget are tracked\n",[132,286,288,290,292,294,297,300,303,306,309],{"class":134,"line":287},5,[132,289,254],{"class":145},[132,291,257],{"class":153},[132,293,260],{"class":149},[132,295,296],{"class":149}," --full-history",[132,298,299],{"class":149}," --",[132,301,302],{"class":153}," .env",[132,304,305],{"class":153}," .env.local",[132,307,308],{"class":153}," .claude/",[132,310,311],{"class":153}," .mcp.json\n",[10,313,314],{},"If either command returns anything, the key is in history and anyone who cloned or forked the repo has a copy.",[111,316,318],{"id":317},"check-3-your-agent-and-mcp-config-files","Check 3: Your agent and MCP config files",[10,320,321],{},"This is the one that's specific to Claude users and the one most often missed.",[123,323,325],{"className":125,"code":324,"language":127,"meta":128,"style":128},"# Look for the key in local agent config, not just source\ngrep -rn \"sk-ant-\" \\\n  --include=\"*.json\" --include=\"*.toml\" --include=\"*.yaml\" --include=\"*.yml\" \\\n  --exclude-dir=node_modules . 2>/dev/null\n\n# And check whether those files are tracked by git\ngit ls-files | grep -E '^\\.claude/|\\.mcp\\.json|mcp.*\\.json'\n",[14,326,327,332,345,371,385,389,395],{"__ignoreMap":128},[132,328,329],{"class":134,"line":135},[132,330,331],{"class":138},"# Look for the key in local agent config, not just source\n",[132,333,334,337,340,343],{"class":134,"line":142},[132,335,336],{"class":145},"grep",[132,338,339],{"class":149}," -rn",[132,341,342],{"class":153}," \"sk-ant-\"",[132,344,180],{"class":149},[132,346,347,350,353,356,359,361,364,366,369],{"class":134,"line":183},[132,348,349],{"class":149},"  --include=",[132,351,352],{"class":153},"\"*.json\"",[132,354,355],{"class":149}," --include=",[132,357,358],{"class":153},"\"*.toml\"",[132,360,355],{"class":149},[132,362,363],{"class":153},"\"*.yaml\"",[132,365,355],{"class":149},[132,367,368],{"class":153},"\"*.yml\"",[132,370,180],{"class":149},[132,372,373,376,379,382],{"class":134,"line":281},[132,374,375],{"class":149},"  --exclude-dir=node_modules",[132,377,378],{"class":153}," .",[132,380,381],{"class":157}," 2>",[132,383,384],{"class":153},"/dev/null\n",[132,386,387],{"class":134,"line":287},[132,388,278],{"emptyLinePlaceholder":277},[132,390,392],{"class":134,"line":391},6,[132,393,394],{"class":138},"# And check whether those files are tracked by git\n",[132,396,398,400,403,405,407,410],{"class":134,"line":397},7,[132,399,254],{"class":145},[132,401,402],{"class":153}," ls-files",[132,404,158],{"class":157},[132,406,161],{"class":145},[132,408,409],{"class":149}," -E",[132,411,412],{"class":153}," '^\\.claude/|\\.mcp\\.json|mcp.*\\.json'\n",[414,415,416],"tip-box",{},[10,417,418,419,421],{},"CheckYourVibe scans your deployed app for keys in the JavaScript bundle, including the ",[14,420,16],{}," pattern. It checks what a stranger's browser can read, which is a different question from what's in your source tree.",[43,423,425],{"id":424},"the-fix-in-six-steps","The Fix, in Six Steps",[427,428,430,435,438,441],"step",{"number":429},"1",[10,431,432],{},[53,433,434],{},"Find which key leaked.",[10,436,437],{},"You probably have more than one key on the account. Before you revoke anything, match the leaked value to a specific key so you don't kill a working production key by accident.",[10,439,440],{},"The Console shows a masked version of each key with the last few characters visible. Compare those characters against the value you found in your bundle, history, or config. Note the key's name and the date it was created.",[10,442,443],{},"If you cannot match it to anything in the list, it may have been created under a different workspace or a different organization. Check both before assuming it's already gone.",[427,445,447,452,455,458,474],{"number":446},"2",[10,448,449],{},[53,450,451],{},"Revoke it in the Anthropic Console.",[10,453,454],{},"Open the Console, go to the API keys list, and delete the exposed key. Deletion takes effect immediately, so if production traffic depends on that key, create the replacement and deploy it first, then delete the old one.",[10,456,457],{},"Order of operations for a live app:",[459,460,461,465,468,471],"ol",{},[462,463,464],"li",{},"Create the new key (with an expiration, see step 5).",[462,466,467],{},"Update the environment variable on your hosting platform.",[462,469,470],{},"Redeploy and confirm the app works.",[462,472,473],{},"Delete the old key.",[10,475,476],{},"For a leaked key on a side project with no traffic, skip the choreography and delete it now.",[427,478,480,485,488,591],{"number":479},"3",[10,481,482],{},[53,483,484],{},"Scrub the key out of git history.",[10,486,487],{},"Rotation stops the old key from working, but if the repo is public or has forks, the value stays visible in history and future readers will assume it's live. Clean it.",[123,489,491],{"className":125,"code":490,"language":127,"meta":128,"style":128},"pip install git-filter-repo\n\n# Remove config files that carried the key\ngit filter-repo --path .env --invert-paths\ngit filter-repo --path .mcp.json --invert-paths\ngit filter-repo --path .claude/settings.local.json --invert-paths\n\n# Or replace the literal value everywhere it appears\necho 'sk-ant-YOUR-LEAKED-VALUE==>REDACTED' > /tmp/replacements.txt\ngit filter-repo --replace-text /tmp/replacements.txt\n",[14,492,493,504,508,513,528,541,554,558,564,579],{"__ignoreMap":128},[132,494,495,498,501],{"class":134,"line":135},[132,496,497],{"class":145},"pip",[132,499,500],{"class":153}," install",[132,502,503],{"class":153}," git-filter-repo\n",[132,505,506],{"class":134,"line":142},[132,507,278],{"emptyLinePlaceholder":277},[132,509,510],{"class":134,"line":183},[132,511,512],{"class":138},"# Remove config files that carried the key\n",[132,514,515,517,520,523,525],{"class":134,"line":281},[132,516,254],{"class":145},[132,518,519],{"class":153}," filter-repo",[132,521,522],{"class":149}," --path",[132,524,302],{"class":153},[132,526,527],{"class":149}," --invert-paths\n",[132,529,530,532,534,536,539],{"class":134,"line":287},[132,531,254],{"class":145},[132,533,519],{"class":153},[132,535,522],{"class":149},[132,537,538],{"class":153}," .mcp.json",[132,540,527],{"class":149},[132,542,543,545,547,549,552],{"class":134,"line":391},[132,544,254],{"class":145},[132,546,519],{"class":153},[132,548,522],{"class":149},[132,550,551],{"class":153}," .claude/settings.local.json",[132,553,527],{"class":149},[132,555,556],{"class":134,"line":397},[132,557,278],{"emptyLinePlaceholder":277},[132,559,561],{"class":134,"line":560},8,[132,562,563],{"class":138},"# Or replace the literal value everywhere it appears\n",[132,565,567,570,573,576],{"class":134,"line":566},9,[132,568,569],{"class":149},"echo",[132,571,572],{"class":153}," 'sk-ant-YOUR-LEAKED-VALUE==>REDACTED'",[132,574,575],{"class":157}," >",[132,577,578],{"class":153}," /tmp/replacements.txt\n",[132,580,582,584,586,589],{"class":134,"line":581},10,[132,583,254],{"class":145},[132,585,519],{"class":153},[132,587,588],{"class":149}," --replace-text",[132,590,578],{"class":153},[10,592,593],{},"Force-push to every remote afterward, and tell collaborators to re-clone rather than pull. A stale local clone will happily push the old history back.",[427,595,597,602,605,608,690,693,902,912],{"number":596},"4",[10,598,599],{},[53,600,601],{},"Move the key server-side.",[10,603,604],{},"If the leak came from a bundled variable, revoking the key just resets the clock. The next key leaks the same way on the next deploy.",[10,606,607],{},"Wrong, because the prefix ships the value to the browser:",[123,609,613],{"className":610,"code":611,"language":612,"meta":128,"style":128},"language-typescript shiki shiki-themes github-dark","// app/components/Chat.tsx\nconst res = await fetch(\"https://api.anthropic.com/v1/messages\", {\n  headers: {\n    \"x-api-key\": process.env.NEXT_PUBLIC_ANTHROPIC_API_KEY!,\n    \"anthropic-version\": \"2023-06-01\",\n  },\n});\n","typescript",[14,614,615,620,646,651,667,680,685],{"__ignoreMap":128},[132,616,617],{"class":134,"line":135},[132,618,619],{"class":138},"// app/components/Chat.tsx\n",[132,621,622,625,628,631,634,637,640,643],{"class":134,"line":142},[132,623,624],{"class":157},"const",[132,626,627],{"class":149}," res",[132,629,630],{"class":157}," =",[132,632,633],{"class":157}," await",[132,635,636],{"class":145}," fetch",[132,638,639],{"class":195},"(",[132,641,642],{"class":153},"\"https://api.anthropic.com/v1/messages\"",[132,644,645],{"class":195},", {\n",[132,647,648],{"class":134,"line":183},[132,649,650],{"class":195},"  headers: {\n",[132,652,653,656,659,661,664],{"class":134,"line":281},[132,654,655],{"class":153},"    \"x-api-key\"",[132,657,658],{"class":195},": process.env.",[132,660,77],{"class":149},[132,662,663],{"class":157},"!",[132,665,666],{"class":195},",\n",[132,668,669,672,675,678],{"class":134,"line":287},[132,670,671],{"class":153},"    \"anthropic-version\"",[132,673,674],{"class":195},": ",[132,676,677],{"class":153},"\"2023-06-01\"",[132,679,666],{"class":195},[132,681,682],{"class":134,"line":391},[132,683,684],{"class":195},"  },\n",[132,686,687],{"class":134,"line":397},[132,688,689],{"class":195},"});\n",[10,691,692],{},"Right, because the key never leaves the server:",[123,694,696],{"className":610,"code":695,"language":612,"meta":128,"style":128},"// app/api/chat/route.ts\nimport Anthropic from \"@anthropic-ai/sdk\";\nimport { NextRequest, NextResponse } from \"next/server\";\n\nconst client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });\n\nexport async function POST(req: NextRequest) {\n  const { message } = await req.json();\n  const reply = await client.messages.create({\n    model: \"claude-sonnet-4-5\",\n    max_tokens: 1024,\n    messages: [{ role: \"user\", content: message }],\n  });\n  return NextResponse.json({ reply });\n}\n",[14,697,698,703,720,734,738,762,766,795,823,843,853,864,876,882,896],{"__ignoreMap":128},[132,699,700],{"class":134,"line":135},[132,701,702],{"class":138},"// app/api/chat/route.ts\n",[132,704,705,708,711,714,717],{"class":134,"line":142},[132,706,707],{"class":157},"import",[132,709,710],{"class":195}," Anthropic ",[132,712,713],{"class":157},"from",[132,715,716],{"class":153}," \"@anthropic-ai/sdk\"",[132,718,719],{"class":195},";\n",[132,721,722,724,727,729,732],{"class":134,"line":183},[132,723,707],{"class":157},[132,725,726],{"class":195}," { NextRequest, NextResponse } ",[132,728,713],{"class":157},[132,730,731],{"class":153}," \"next/server\"",[132,733,719],{"class":195},[132,735,736],{"class":134,"line":281},[132,737,278],{"emptyLinePlaceholder":277},[132,739,740,742,745,747,750,753,756,759],{"class":134,"line":287},[132,741,624],{"class":157},[132,743,744],{"class":149}," client",[132,746,630],{"class":157},[132,748,749],{"class":157}," new",[132,751,752],{"class":145}," Anthropic",[132,754,755],{"class":195},"({ apiKey: process.env.",[132,757,758],{"class":149},"ANTHROPIC_API_KEY",[132,760,761],{"class":195}," });\n",[132,763,764],{"class":134,"line":391},[132,765,278],{"emptyLinePlaceholder":277},[132,767,768,771,774,777,780,782,786,789,792],{"class":134,"line":397},[132,769,770],{"class":157},"export",[132,772,773],{"class":157}," async",[132,775,776],{"class":157}," function",[132,778,779],{"class":145}," POST",[132,781,639],{"class":195},[132,783,785],{"class":784},"s9osk","req",[132,787,788],{"class":157},":",[132,790,791],{"class":145}," NextRequest",[132,793,794],{"class":195},") {\n",[132,796,797,800,803,806,809,812,814,817,820],{"class":134,"line":560},[132,798,799],{"class":157},"  const",[132,801,802],{"class":195}," { ",[132,804,805],{"class":149},"message",[132,807,808],{"class":195}," } ",[132,810,811],{"class":157},"=",[132,813,633],{"class":157},[132,815,816],{"class":195}," req.",[132,818,819],{"class":145},"json",[132,821,822],{"class":195},"();\n",[132,824,825,827,830,832,834,837,840],{"class":134,"line":566},[132,826,799],{"class":157},[132,828,829],{"class":149}," reply",[132,831,630],{"class":157},[132,833,633],{"class":157},[132,835,836],{"class":195}," client.messages.",[132,838,839],{"class":145},"create",[132,841,842],{"class":195},"({\n",[132,844,845,848,851],{"class":134,"line":581},[132,846,847],{"class":195},"    model: ",[132,849,850],{"class":153},"\"claude-sonnet-4-5\"",[132,852,666],{"class":195},[132,854,856,859,862],{"class":134,"line":855},11,[132,857,858],{"class":195},"    max_tokens: ",[132,860,861],{"class":149},"1024",[132,863,666],{"class":195},[132,865,867,870,873],{"class":134,"line":866},12,[132,868,869],{"class":195},"    messages: [{ role: ",[132,871,872],{"class":153},"\"user\"",[132,874,875],{"class":195},", content: message }],\n",[132,877,879],{"class":134,"line":878},13,[132,880,881],{"class":195},"  });\n",[132,883,885,888,891,893],{"class":134,"line":884},14,[132,886,887],{"class":157},"  return",[132,889,890],{"class":195}," NextResponse.",[132,892,819],{"class":145},[132,894,895],{"class":195},"({ reply });\n",[132,897,899],{"class":134,"line":898},15,[132,900,901],{"class":195},"}\n",[10,903,904,905,908,909,911],{},"Your component calls ",[14,906,907],{},"/api/chat",". Your Route Handler calls Anthropic. On Vite-based builders (Lovable, most SvelteKit apps), the same rule applies to ",[14,910,40],{},": drop the prefix and put the call in a Netlify function, a Supabase Edge Function, or your own backend.",[913,914,915,928],"table",{},[916,917,918],"thead",{},[919,920,921,925],"tr",{},[922,923,924],"th",{},"Wrong (reaches the browser)",[922,926,927],{},"Right (server only)",[929,930,931,942,952],"tbody",{},[919,932,933,938],{},[934,935,936],"td",{},[14,937,77],{},[934,939,940],{},[14,941,758],{},[919,943,944,948],{},[934,945,946],{},[14,947,80],{},[934,949,950],{},[14,951,758],{},[919,953,954,959],{},[934,955,956],{},[14,957,958],{},"NEXT_PUBLIC_CLAUDE_KEY",[934,960,961],{},[14,962,758],{},[427,964,966,971],{"number":965},"5",[10,967,968],{},[53,969,970],{},"Set an expiration on the replacement key.",[10,972,973],{},"This option did not exist before July 8, 2026. Now, when you create a key, you can pick a preset lifetime, a custom date, or Never. Choose something other than Never. Details are in the next section.",[427,975,977,982,1096,1099],{"number":976},"6",[10,978,979],{},[53,980,981],{},"Add pre-commit scanning so it can't happen again.",[123,983,985],{"className":125,"code":984,"language":127,"meta":128,"style":128},"brew install gitleaks   # or: go install github.com/zricethezav/gitleaks/v8@latest\n\n# Scan what you already have\ngitleaks detect --source . --verbose\n\n# Block future commits\ncat > .git/hooks/pre-commit \u003C\u003C 'EOF'\n#!/bin/sh\ngitleaks protect --staged --verbose\nif [ $? -ne 0 ]; then\n  echo \"Gitleaks found potential secrets. Commit blocked.\"\n  exit 1\nfi\nEOF\nchmod +x .git/hooks/pre-commit\n",[14,986,987,1000,1004,1009,1025,1029,1034,1050,1055,1060,1065,1070,1075,1080,1085],{"__ignoreMap":128},[132,988,989,992,994,997],{"class":134,"line":135},[132,990,991],{"class":145},"brew",[132,993,500],{"class":153},[132,995,996],{"class":153}," gitleaks",[132,998,999],{"class":138},"   # or: go install github.com/zricethezav/gitleaks/v8@latest\n",[132,1001,1002],{"class":134,"line":142},[132,1003,278],{"emptyLinePlaceholder":277},[132,1005,1006],{"class":134,"line":183},[132,1007,1008],{"class":138},"# Scan what you already have\n",[132,1010,1011,1014,1017,1020,1022],{"class":134,"line":281},[132,1012,1013],{"class":145},"gitleaks",[132,1015,1016],{"class":153}," detect",[132,1018,1019],{"class":149}," --source",[132,1021,378],{"class":153},[132,1023,1024],{"class":149}," --verbose\n",[132,1026,1027],{"class":134,"line":287},[132,1028,278],{"emptyLinePlaceholder":277},[132,1030,1031],{"class":134,"line":391},[132,1032,1033],{"class":138},"# Block future commits\n",[132,1035,1036,1039,1041,1044,1047],{"class":134,"line":397},[132,1037,1038],{"class":145},"cat",[132,1040,575],{"class":157},[132,1042,1043],{"class":153}," .git/hooks/pre-commit",[132,1045,1046],{"class":157}," \u003C\u003C",[132,1048,1049],{"class":153}," 'EOF'\n",[132,1051,1052],{"class":134,"line":560},[132,1053,1054],{"class":153},"#!/bin/sh\n",[132,1056,1057],{"class":134,"line":566},[132,1058,1059],{"class":153},"gitleaks protect --staged --verbose\n",[132,1061,1062],{"class":134,"line":581},[132,1063,1064],{"class":153},"if [ $? -ne 0 ]; then\n",[132,1066,1067],{"class":134,"line":855},[132,1068,1069],{"class":153},"  echo \"Gitleaks found potential secrets. Commit blocked.\"\n",[132,1071,1072],{"class":134,"line":866},[132,1073,1074],{"class":153},"  exit 1\n",[132,1076,1077],{"class":134,"line":878},[132,1078,1079],{"class":153},"fi\n",[132,1081,1082],{"class":134,"line":884},[132,1083,1084],{"class":153},"EOF\n",[132,1086,1087,1090,1093],{"class":134,"line":898},[132,1088,1089],{"class":145},"chmod",[132,1091,1092],{"class":153}," +x",[132,1094,1095],{"class":153}," .git/hooks/pre-commit\n",[10,1097,1098],{},"Then keep agent config out of the repo entirely:",[123,1100,1102],{"className":125,"code":1101,"language":127,"meta":128,"style":128},"cat >> .gitignore \u003C\u003C 'EOF'\n.env\n.env.local\n.claude/settings.local.json\n.mcp.json\nEOF\n",[14,1103,1104,1118,1123,1128,1133,1138],{"__ignoreMap":128},[132,1105,1106,1108,1111,1114,1116],{"class":134,"line":135},[132,1107,1038],{"class":145},[132,1109,1110],{"class":157}," >>",[132,1112,1113],{"class":153}," .gitignore",[132,1115,1046],{"class":157},[132,1117,1049],{"class":153},[132,1119,1120],{"class":134,"line":142},[132,1121,1122],{"class":153},".env\n",[132,1124,1125],{"class":134,"line":183},[132,1126,1127],{"class":153},".env.local\n",[132,1129,1130],{"class":134,"line":281},[132,1131,1132],{"class":153},".claude/settings.local.json\n",[132,1134,1135],{"class":134,"line":287},[132,1136,1137],{"class":153},".mcp.json\n",[132,1139,1140],{"class":134,"line":391},[132,1141,1084],{"class":153},[43,1143,1145],{"id":1144},"your-key-can-leak-from-your-laptop-not-just-your-repo","Your Key Can Leak From Your Laptop, Not Just Your Repo",[10,1147,1148],{},"Everything above assumes the key escaped through code you wrote. There's a second path that has nothing to do with your repo.",[10,1150,1151,1152,1155,1156,1159,1160,68],{},"On July 11, 2026, the ",[14,1153,1154],{},"jscrambler"," npm package was published with a backdoor. The payload was a cross-platform infostealer, and its target list is the interesting part: alongside cloud credentials and crypto wallets, it specifically harvests ",[53,1157,1158],{},"AI coding assistant configuration files",", including Claude, Cursor, and Windsurf configs. That's where a lot of people keep an ",[14,1161,758],{},[10,1163,1164],{},"Affected versions: 8.14.0, 8.16.0, 8.17.0, 8.18.0, and 8.20.0. Version 8.22.0 is clean.",[123,1166,1168],{"className":125,"code":1167,"language":127,"meta":128,"style":128},"# Do you have it, directly or as a transitive dependency?\nnpm ls jscrambler\n\n# If you do, check which version resolved\nnpm ls jscrambler --all | grep jscrambler\n",[14,1169,1170,1175,1186,1190,1195],{"__ignoreMap":128},[132,1171,1172],{"class":134,"line":135},[132,1173,1174],{"class":138},"# Do you have it, directly or as a transitive dependency?\n",[132,1176,1177,1180,1183],{"class":134,"line":142},[132,1178,1179],{"class":145},"npm",[132,1181,1182],{"class":153}," ls",[132,1184,1185],{"class":153}," jscrambler\n",[132,1187,1188],{"class":134,"line":183},[132,1189,278],{"emptyLinePlaceholder":277},[132,1191,1192],{"class":134,"line":281},[132,1193,1194],{"class":138},"# If you do, check which version resolved\n",[132,1196,1197,1199,1201,1204,1206,1208,1210],{"class":134,"line":287},[132,1198,1179],{"class":145},[132,1200,1182],{"class":153},[132,1202,1203],{"class":153}," jscrambler",[132,1205,260],{"class":149},[132,1207,158],{"class":157},[132,1209,161],{"class":145},[132,1211,1185],{"class":153},[10,1213,1214],{},"If any of the affected versions show up, upgrade to 8.22.0 and then revoke every credential that was sitting in a config file on that machine. Not just the Anthropic key. The whole drawer.",[1216,1217,1218],"warning-box",{},[10,1219,1220],{},"This is a supply-chain attack on an npm package. Anthropic was not breached, and Anthropic's systems were not involved. The distinction matters for your response: there is nothing to wait for from a provider, and nobody is going to rotate the key for you. The credential was read off a developer machine, which means the fix is entirely on your side.",[10,1222,1223,1224,68],{},"Full write-up from Socket: ",[1225,1226,1230],"a",{"href":1227,"rel":1228},"https://socket.dev/blog/jscrambler-supply-chain-attack",[1229],"nofollow","socket.dev/blog/jscrambler-supply-chain-attack",[43,1232,1234],{"id":1233},"set-an-expiration-on-your-new-key","Set an Expiration on Your New Key",[10,1236,1237],{},"This is the newest thing you can do about Anthropic key exposure, and almost nobody has turned it on yet.",[10,1239,1240],{},"Since July 8, 2026, Anthropic API keys support optional expiration dates. When you create a key, you pick one of:",[1242,1243,1244,1250,1256],"ul",{},[462,1245,99,1246,1249],{},[53,1247,1248],{},"preset"," lifetime.",[462,1251,99,1252,1255],{},[53,1253,1254],{},"custom"," expiration date.",[462,1257,1258,1261],{},[53,1259,1260],{},"Never",", which is the old behavior.",[10,1263,1264,1265,1268],{},"The Admin API exposes the setting as an ",[14,1266,1267],{},"expires_at"," field, so you can audit it programmatically instead of clicking through the Console. Anthropic also sends email warnings ahead of expiry for keys with a lifetime of seven days or more, which means you get a heads-up rather than a surprise 401 in production.",[10,1270,1271],{},"Two things to understand before you rely on it:",[10,1273,1274,1277],{},[53,1275,1276],{},"Existing keys were not changed."," This is not retroactive. Every key you made before July 8, 2026 is still set to Never, and it will stay that way until you delete it or create a replacement with an expiry. Go look at your key list. The old ones are the ones to worry about.",[10,1279,1280,1283],{},[53,1281,1282],{},"Expiration is damage control, not prevention."," A 90-day key that leaks on day one is exposed for 90 days. What it does is cap the blast radius of the leaks you never find out about, which is the majority of them. A key created in January with Never set is a permanent liability. The same key with a 90-day expiry is a temporary one.",[10,1285,1286],{},"A reasonable default: 90 days for keys used by deployed services (long enough that rotation isn't constant, short enough that a forgotten leak dies), and something much shorter for keys you make to test something locally. The seven-day threshold on email warnings is a useful floor. Below that, you won't get a reminder.",[414,1288,1289],{},[10,1290,1291,1292,1295,1296,1299,1300,1302],{},"When you rotate, name the key after where it lives (",[14,1293,1294],{},"prod-api-render",", ",[14,1297,1298],{},"local-dev-laptop",") instead of leaving it as the default. When you later find a leaked ",[14,1301,16],{}," value, the name tells you instantly which system breaks when you delete it. Step 1 of this guide gets much faster.",[43,1304,1306],{"id":1305},"faq","FAQ",[1308,1309,1310,1332,1344,1350,1356],"faq-section",{},[1311,1312,1314],"faq-item",{"question":1313},"Is it safe to put my Anthropic API key in a .env file?",[10,1315,1316,1317,1320,1321,37,1323,1325,1326,1328,1329,1331],{},"Yes, as long as that ",[14,1318,1319],{},".env"," file is git-ignored and the variable has no ",[14,1322,36],{},[14,1324,40],{}," prefix. A ",[14,1327,1319],{}," file read by a server process is the normal way to store the key. It stops being safe the moment the file gets committed, the variable name gets a public prefix that bundles it into your frontend, or a build step copies ",[14,1330,1319],{}," into a deployed artifact that anyone can fetch.",[1311,1333,1335],{"question":1334},"What can someone do with my sk-ant key?",[10,1336,1337,1338,1340,1341,1343],{},"They can make Claude API calls billed to your account until you revoke the key or hit your spend limit. That's the main damage: your credits, your rate limits, your invoice. A standard ",[14,1339,16],{}," key doesn't give access to your Claude.ai conversations or your account login. Keys with the ",[14,1342,32],{}," prefix are a bigger problem, because Admin API keys manage organization-level resources including other API keys.",[1311,1345,1347],{"question":1346},"Do Anthropic API keys expire?",[10,1348,1349],{},"Only if you asked for it. Anthropic added optional expiration dates on July 8, 2026, with preset options, a custom date, or Never. Existing keys were explicitly unaffected, so any key created before that date runs forever until someone deletes it. That's why a key pasted into a repo in early 2026 is still live today.",[1311,1351,1353],{"question":1352},"How do I know if my leaked Anthropic key was actually used?",[10,1354,1355],{},"Check usage in the Anthropic Console for the exposure window and look for volume you can't account for: requests at hours you weren't working, models you don't use, token counts well above your normal pattern. A flat graph isn't proof of safety, though. Some attackers use a stolen key slowly to stay under the radar. Revoke it either way.",[1311,1357,1359],{"question":1358},"Can I call the Anthropic API directly from my frontend?",[10,1360,1361],{},"No. Any key shipped to a browser is public, and no CORS setting or obfuscation changes that. Put the call in a Next.js Route Handler, a Netlify or Vercel function, a Supabase Edge Function, or your own backend. Your frontend calls your endpoint, and your endpoint is the only thing holding the key.",[1363,1364,1365,1371,1376],"related-articles",{},[1366,1367],"related-card",{"description":1368,"href":1369,"title":1370},"How to review what Claude Code generates, keep secrets out of agent config, and deploy the result safely.","/blog/guides/claude-code","Claude Code Security Guide",[1366,1372],{"description":1373,"href":1374,"title":1375},"Security analysis of Anthropic's coding assistant: data handling, generated code quality, and what to check before production.","/blog/is-safe/claude-code","Is Claude Code Safe?",[1366,1377],{"description":1378,"href":1379,"title":1380},"Emergency rotation without downtime, with per-service steps for Stripe, OpenAI, Supabase, and others.","/blog/how-to/rotate-api-keys","How to Rotate API Keys",[1382,1383,1386],"cta-box",{"href":1384,"label":1385},"/","Start Free Scan",[10,1387,1388,1389,1391],{},"Check your deployed app for ",[14,1390,16],{}," keys in the JavaScript bundle, exposed environment variables, and other secrets a visitor can read. Free scan, no signup required.",[1393,1394,1395],"style",{},"html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":128,"searchDepth":142,"depth":142,"links":1397},[1398,1399,1404,1405,1406,1407],{"id":45,"depth":142,"text":46},{"id":105,"depth":142,"text":106,"children":1400},[1401,1402,1403],{"id":113,"depth":183,"text":114},{"id":235,"depth":183,"text":236},{"id":317,"depth":183,"text":318},{"id":424,"depth":142,"text":425},{"id":1144,"depth":142,"text":1145},{"id":1233,"depth":142,"text":1234},{"id":1305,"depth":142,"text":1306},"how-to","2026-07-31","An sk-ant key created before July 8, 2026 never expires on its own. Find where it leaked, revoke it in the Anthropic Console, scrub git history, and set an expiry on the replacement.",false,"md",[1414,1416,1418,1420,1422],{"question":1313,"answer":1415},"Yes, as long as the .env file is git-ignored and the variable has no NEXT_PUBLIC_ or VITE_ prefix. A .env file read by a server process is the normal way to store the key. It stops being safe the moment the file gets committed, or the variable name gets a public prefix that bundles it into your frontend, or a build tool copies .env into a deployed artifact that is publicly readable.",{"question":1334,"answer":1417},"They can make Claude API calls billed to your account until you revoke the key or hit your spend limit. That is the main damage: your credits, your rate limits, your invoice. A standard sk-ant- key does not give access to your Claude.ai conversations or your account login. Keys with the sk-ant-admin prefix are a bigger problem, because Admin API keys manage organization-level resources including other API keys.",{"question":1346,"answer":1419},"Only if you asked for it. Anthropic added optional expiration dates on July 8, 2026, with preset options, a custom date, or Never. Existing keys were explicitly unaffected by that change, so any key created before that date runs forever until someone deletes it. That is why a key pasted into a repo in early 2026 is still live today.",{"question":1352,"answer":1421},"Check usage in the Anthropic Console for the exposure window and look for request volume you cannot account for: spikes at hours you were not working, models you do not use, or token counts far above your normal pattern. Absence of a spike is not proof of safety. Some attackers sit on a working key and use it slowly to avoid attention. Revoke it either way.",{"question":1358,"answer":1423},"No. Any key shipped to a browser is public, and no CORS setting or obfuscation changes that. Put the call in a Next.js Route Handler, a Netlify or Vercel function, a Supabase Edge Function, or your own backend. Your frontend calls your endpoint, your endpoint holds the key.","yellow",null,"anthropic api key exposed, claude api key leaked, sk-ant key exposed, revoke anthropic api key, anthropic api key expiration, rotate claude api key, mcp config api key",{},"Anthropic API key leaked? Find it in your bundle, git history, or MCP config, revoke it, move it server-side, and put an expiration date on the new one.","/blog/how-to/fix-anthropic-api-key-exposure","10 min read","[object Object]","HowTo",{"title":5,"description":1410},{"loc":1429},"blog/how-to/fix-anthropic-api-key-exposure",[],"summary_large_image","lulQSWvFwK3mKJ-oq9E0xGyMAFM08JzE-Gq_LWAGpwo",1785543175821]