[{"data":1,"prerenderedAt":785},["ShallowReactive",2],{"blog-how-to/detect-secrets-in-git":3},{"id":4,"title":5,"body":6,"category":757,"date":758,"dateModified":758,"description":759,"draft":760,"extension":761,"faq":762,"featured":760,"headerVariant":770,"image":771,"keywords":772,"meta":773,"navigation":381,"ogDescription":774,"ogTitle":771,"path":775,"readTime":776,"schemaOrg":777,"schemaType":778,"seo":779,"sitemap":780,"stem":781,"tags":782,"twitterCard":783,"__hash__":784},"blog/blog/how-to/detect-secrets-in-git.md","Detect Secrets in Git: Tools and Patterns (2026)",{"type":7,"value":8,"toc":734},"minimark",[9,13,16,27,32,39,45,48,52,71,87,91,94,110,116,120,123,228,250,278,282,285,314,320,324,327,489,493,496,600,607,618,622,659,699,718,730],[10,11,12],"p",{},"GitHub's automated secret scanning found 12.8 million tokens exposed in public repositories in 2023 alone. Those were only the repos GitHub could scan. Your private repo, your team's fork, a contractor's clone: none of those show up in that count.",[10,14,15],{},"The good news: two free tools will scan your entire git history in under two minutes, and you can block future leaks with a one-time hook setup.",[17,18,19],"tldr",{},[10,20,21,22,26],{},"Run ",[23,24,25],"code",{},"gitleaks git --source ."," to scan your full commit history for secrets. Add a pre-commit hook with gitleaks to block secrets before they're committed. Add a CI step to catch anything that slips past the hook. If you find a secret in history, rotate it immediately. Deleting the file does nothing: the commit history still has it.",[28,29,31],"h2",{"id":30},"two-tools-worth-knowing","Two tools worth knowing",[10,33,34,38],{},[35,36,37],"strong",{},"Gitleaks"," is the faster, more widely used option. It detects 150+ secret types (Stripe keys, AWS credentials, GitHub tokens, OpenAI keys, and more), scans staged files and full history, and exits with a non-zero code when secrets are found (which makes it easy to fail CI).",[10,40,41,44],{},[35,42,43],{},"detect-secrets"," from IBM takes a different approach: it creates a baseline file listing known secrets so you can audit them, rather than blocking on every match. Useful when you have a codebase with intentional test credentials or documentation examples.",[10,46,47],{},"This guide focuses on gitleaks for the pre-commit and CI cases, and shows detect-secrets for baseline auditing.",[28,49,51],{"id":50},"step-1-scan-your-working-tree","Step 1: Scan your working tree",[53,54,56,61],"step",{"number":55},"1",[57,58,60],"h3",{"id":59},"install-gitleaks","Install gitleaks",[62,63,68],"pre",{"className":64,"code":66,"language":67},[65],"language-text","# macOS\nbrew install gitleaks\n\n# Linux / Windows\n# Download from https://github.com/gitleaks/gitleaks/releases\n# or: go install github.com/zricethezav/gitleaks/v8@latest\n","text",[23,69,66],{"__ignoreMap":70},"",[53,72,74,78,84],{"number":73},"2",[57,75,77],{"id":76},"scan-staged-and-tracked-files","Scan staged and tracked files",[62,79,82],{"className":80,"code":81,"language":67},[65],"# Scan the current working directory (tracked files only)\ngitleaks detect --source .\n\n# Scan only files staged for the next commit\ngitleaks detect --staged\n",[23,83,81],{"__ignoreMap":70},[10,85,86],{},"This checks the files as they exist right now. It does not check history.",[28,88,90],{"id":89},"step-2-scan-commit-history","Step 2: Scan commit history",[10,92,93],{},"Scanning current files is not enough. A secret committed six months ago and then deleted is still in every commit between then and now. Anyone who clones your repo can find it.",[53,95,97,101,107],{"number":96},"3",[57,98,100],{"id":99},"walk-the-full-git-history","Walk the full git history",[62,102,105],{"className":103,"code":104,"language":67},[65],"# Scan every commit reachable from the current HEAD\ngitleaks git --source .\n\n# Save the report as JSON (useful for CI and auditing)\ngitleaks git --source . --report-path gitleaks-report.json\n",[23,106,104],{"__ignoreMap":70},[10,108,109],{},"The output lists the secret type, the file, the commit SHA, and the first few characters of the value. If you see hits, rotate those credentials before doing anything else.",[111,112,113],"warning-box",{},[10,114,115],{},"Rotating comes first. Cleaning history (via BFG or git filter-repo) removes the secret from future clones, but anyone who already cloned the repo has the secret. Rotate first, clean second.",[28,117,119],{"id":118},"step-3-block-secrets-before-theyre-committed","Step 3: Block secrets before they're committed",[10,121,122],{},"A one-time history scan catches the past. A pre-commit hook catches the future.",[53,124,126,130,136,143,212,215,221],{"number":125},"4",[57,127,129],{"id":128},"set-up-the-pre-commit-framework","Set up the pre-commit framework",[62,131,134],{"className":132,"code":133,"language":67},[65],"pip install pre-commit\n",[23,135,133],{"__ignoreMap":70},[10,137,138,139,142],{},"Create ",[23,140,141],{},".pre-commit-config.yaml"," in your repo root:",[62,144,148],{"className":145,"code":146,"language":147,"meta":70,"style":70},"language-yaml shiki shiki-themes github-light github-dark","repos:\n  - repo: https://github.com/gitleaks/gitleaks\n    rev: v8.24.0\n    hooks:\n      - id: gitleaks\n","yaml",[23,149,150,163,179,190,198],{"__ignoreMap":70},[151,152,155,159],"span",{"class":153,"line":154},"line",1,[151,156,158],{"class":157},"s9eBZ","repos",[151,160,162],{"class":161},"sVt8B",":\n",[151,164,166,169,172,175],{"class":153,"line":165},2,[151,167,168],{"class":161},"  - ",[151,170,171],{"class":157},"repo",[151,173,174],{"class":161},": ",[151,176,178],{"class":177},"sZZnC","https://github.com/gitleaks/gitleaks\n",[151,180,182,185,187],{"class":153,"line":181},3,[151,183,184],{"class":157},"    rev",[151,186,174],{"class":161},[151,188,189],{"class":177},"v8.24.0\n",[151,191,193,196],{"class":153,"line":192},4,[151,194,195],{"class":157},"    hooks",[151,197,162],{"class":161},[151,199,201,204,207,209],{"class":153,"line":200},5,[151,202,203],{"class":161},"      - ",[151,205,206],{"class":157},"id",[151,208,174],{"class":161},[151,210,211],{"class":177},"gitleaks\n",[10,213,214],{},"Then install the hook:",[62,216,219],{"className":217,"code":218,"language":67},[65],"pre-commit install\n",[23,220,218],{"__ignoreMap":70},[10,222,223,224,227],{},"From now on, every ",[23,225,226],{},"git commit"," runs gitleaks against the staged diff. If a secret matches, the commit is blocked and the output tells you what was found.",[229,230,231],"tip-box",{},[10,232,233,234,237,238,241,242,245,246,249],{},"Pin the ",[23,235,236],{},"rev"," value to a specific tag (",[23,239,240],{},"v8.24.0","), not ",[23,243,244],{},"latest"," or ",[23,247,248],{},"main",". This avoids surprise breakage when gitleaks releases a new version with stricter rules.",[53,251,253,257,260,266,269,275],{"number":252},"5",[57,254,256],{"id":255},"test-it-works","Test it works",[10,258,259],{},"Create a file with a fake secret pattern and try to commit it:",[62,261,264],{"className":262,"code":263,"language":67},[65],"echo \"STRIPE_SECRET_KEY=sk_live_faketest1234567890abcdef\" > /tmp/test-secret.txt\ngit add /tmp/test-secret.txt\ngit commit -m \"test\"\n",[23,265,263],{"__ignoreMap":70},[10,267,268],{},"Gitleaks should block the commit and print:",[62,270,273],{"className":271,"code":272,"language":67},[65],"Finding:     STRIPE_SECRET_KEY=sk_live_fake...\nSecret:      sk_live_fake...\nRuleID:      stripe-api-key\nEntropy:     3.58\nFile:        test-secret.txt\n",[23,274,272],{"__ignoreMap":70},[10,276,277],{},"Delete the test file before continuing.",[28,279,281],{"id":280},"step-4-add-detect-secrets-for-a-baseline-audit","Step 4: Add detect-secrets for a baseline audit",[10,283,284],{},"When you inherit a codebase, you may not know what secrets are intentional (test credentials, documentation snippets) and what are real. detect-secrets handles this with a baseline workflow.",[53,286,288,292,298,305,311],{"number":287},"6",[57,289,291],{"id":290},"create-a-baseline","Create a baseline",[62,293,296],{"className":294,"code":295,"language":67},[65],"pip install detect-secrets\n\n# Scan and write a baseline of all current \"secrets\" (real and false positives)\ndetect-secrets scan > .secrets.baseline\n",[23,297,295],{"__ignoreMap":70},[10,299,300,301,304],{},"Open ",[23,302,303],{},".secrets.baseline"," in your editor. For each entry, decide: is this a real secret or a false positive? Mark false positives as audited:",[62,306,309],{"className":307,"code":308,"language":67},[65],"detect-secrets audit .secrets.baseline\n",[23,310,308],{"__ignoreMap":70},[10,312,313],{},"Commit the baseline file. Future scans flag only new secrets not in the baseline.",[315,316,317],"info-box",{},[10,318,319],{},"detect-secrets works alongside gitleaks, not instead of it. Use detect-secrets for the initial audit of an existing codebase, gitleaks for the ongoing hook and CI enforcement.",[28,321,323],{"id":322},"step-5-add-cicd-scanning","Step 5: Add CI/CD scanning",[10,325,326],{},"Pre-commit hooks run locally. CI catches anything that bypassed the hook (a commit from GitHub's web UI, a squash-merge, a team member without the hook installed).",[53,328,330,334,341,483],{"number":329},"7",[57,331,333],{"id":332},"github-actions-workflow","GitHub Actions workflow",[10,335,336,337,340],{},"Add this to ",[23,338,339],{},".github/workflows/secret-scan.yml",":",[62,342,344],{"className":145,"code":343,"language":147,"meta":70,"style":70},"name: Secret Scan\non: [push, pull_request]\n\njobs:\n  gitleaks:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0          # full history for history scan\n      - uses: gitleaks/gitleaks-action@v2\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n",[23,345,346,356,377,383,390,397,408,416,429,437,452,464,472],{"__ignoreMap":70},[151,347,348,351,353],{"class":153,"line":154},[151,349,350],{"class":157},"name",[151,352,174],{"class":161},[151,354,355],{"class":177},"Secret Scan\n",[151,357,358,362,365,368,371,374],{"class":153,"line":165},[151,359,361],{"class":360},"sj4cs","on",[151,363,364],{"class":161},": [",[151,366,367],{"class":177},"push",[151,369,370],{"class":161},", ",[151,372,373],{"class":177},"pull_request",[151,375,376],{"class":161},"]\n",[151,378,379],{"class":153,"line":181},[151,380,382],{"emptyLinePlaceholder":381},true,"\n",[151,384,385,388],{"class":153,"line":192},[151,386,387],{"class":157},"jobs",[151,389,162],{"class":161},[151,391,392,395],{"class":153,"line":200},[151,393,394],{"class":157},"  gitleaks",[151,396,162],{"class":161},[151,398,400,403,405],{"class":153,"line":399},6,[151,401,402],{"class":157},"    runs-on",[151,404,174],{"class":161},[151,406,407],{"class":177},"ubuntu-latest\n",[151,409,411,414],{"class":153,"line":410},7,[151,412,413],{"class":157},"    steps",[151,415,162],{"class":161},[151,417,419,421,424,426],{"class":153,"line":418},8,[151,420,203],{"class":161},[151,422,423],{"class":157},"uses",[151,425,174],{"class":161},[151,427,428],{"class":177},"actions/checkout@v4\n",[151,430,432,435],{"class":153,"line":431},9,[151,433,434],{"class":157},"        with",[151,436,162],{"class":161},[151,438,440,443,445,448],{"class":153,"line":439},10,[151,441,442],{"class":157},"          fetch-depth",[151,444,174],{"class":161},[151,446,447],{"class":360},"0",[151,449,451],{"class":450},"sJ8bj","          # full history for history scan\n",[151,453,455,457,459,461],{"class":153,"line":454},11,[151,456,203],{"class":161},[151,458,423],{"class":157},[151,460,174],{"class":161},[151,462,463],{"class":177},"gitleaks/gitleaks-action@v2\n",[151,465,467,470],{"class":153,"line":466},12,[151,468,469],{"class":157},"        env",[151,471,162],{"class":161},[151,473,475,478,480],{"class":153,"line":474},13,[151,476,477],{"class":157},"          GITHUB_TOKEN",[151,479,174],{"class":161},[151,481,482],{"class":177},"${{ secrets.GITHUB_TOKEN }}\n",[10,484,485,488],{},[23,486,487],{},"fetch-depth: 0"," is required. Without it, Actions does a shallow clone and gitleaks only sees the tip commit, missing everything in history.",[28,490,492],{"id":491},"what-gitleaks-detects","What gitleaks detects",[10,494,495],{},"Gitleaks ships with rules for 150+ secret types. A few common ones relevant to vibe-coded apps:",[497,498,499,512],"table",{},[500,501,502],"thead",{},[503,504,505,509],"tr",{},[506,507,508],"th",{},"Service",[506,510,511],{},"Pattern matched",[513,514,515,532,543,554,571,579],"tbody",{},[503,516,517,521],{},[518,519,520],"td",{},"Stripe",[518,522,523,370,526,370,529],{},[23,524,525],{},"sk_live_...",[23,527,528],{},"rk_live_...",[23,530,531],{},"sk_test_...",[503,533,534,537],{},[518,535,536],{},"OpenAI",[518,538,539,542],{},[23,540,541],{},"sk-..."," (51 chars)",[503,544,545,548],{},[518,546,547],{},"AWS",[518,549,550,553],{},[23,551,552],{},"AKIA..."," access key IDs",[503,555,556,559],{},[518,557,558],{},"GitHub",[518,560,561,370,564,370,567,570],{},[23,562,563],{},"ghp_...",[23,565,566],{},"ghs_...",[23,568,569],{},"ghr_..."," tokens",[503,572,573,576],{},[518,574,575],{},"Supabase",[518,577,578],{},"service role keys and JWT secrets",[503,580,581,584],{},[518,582,583],{},"Generic",[518,585,586,587,370,590,370,593,370,596,599],{},"High-entropy strings assigned to ",[23,588,589],{},"API_KEY",[23,591,592],{},"SECRET",[23,594,595],{},"PASSWORD",[23,597,598],{},"TOKEN"," variable names",[10,601,602,603,606],{},"The generic high-entropy rule catches a lot of custom secrets that don't match a specific service pattern. You may get false positives on base64-encoded content. Add those paths to a ",[23,604,605],{},".gitleaksignore"," file.",[229,608,609],{},[10,610,138,611,613,614,617],{},[23,612,605],{}," in your repo root and add paths or commit SHAs for known false positives. Format is one path or ",[23,615,616],{},"{sha}:{filepath}"," per line.",[28,619,621],{"id":620},"what-to-do-when-you-find-a-secret","What to do when you find a secret",[623,624,625,632,638,644,650],"ol",{},[626,627,628,631],"li",{},[35,629,630],{},"Rotate the credential"," in the service dashboard (Stripe, OpenAI, AWS, etc.) before anything else",[626,633,634,637],{},[35,635,636],{},"Check access logs"," for the service to see if the key was used by anyone other than you",[626,639,640,643],{},[35,641,642],{},"Clean git history"," if the repo is or was ever public (BFG Repo Cleaner is the fastest path)",[626,645,646,649],{},[35,647,648],{},"Force-push the cleaned history"," and have all team members re-clone",[626,651,652,655,656,658],{},[35,653,654],{},"Add the secret pattern"," to gitleaks config or ",[23,657,605],{}," so future scans don't report the now-rotated key",[660,661,662,669,677,683,693],"faq-section",{},[663,664,666],"faq-item",{"question":665},"What is the best tool to detect secrets in git?",[10,667,668],{},"Gitleaks is the most widely used open-source tool for detecting secrets in git repos. It scans both the working tree and commit history, detects 150+ secret types, and is easy to wire into pre-commit hooks and CI/CD. detect-secrets from IBM is a good alternative with a baseline workflow that reduces false positives.",[663,670,672],{"question":671},"How do I scan git history for secrets?",[10,673,21,674,676],{},[23,675,25],{}," to walk every commit in the current repository. This scans the full history, not just the current files. If secrets are found in old commits, rotate the credentials immediately. They are already public, even if the file has since been deleted.",[663,678,680],{"question":679},"Does deleting a file remove secrets from git?",[10,681,682],{},"No. Deleting a file only removes it from the current commit. The secret still exists in every previous commit where the file was present. Anyone who clones your repo can check out the old commit and read the secret. Use BFG Repo Cleaner or git filter-repo to rewrite history after rotating credentials.",[663,684,686],{"question":685},"How do I prevent secrets from being committed in the first place?",[10,687,688,689,692],{},"Use a pre-commit hook that runs gitleaks before each commit. The hook blocks the commit if a secret is detected. Wire it via the pre-commit framework so all teammates get it automatically when they run ",[23,690,691],{},"pip install pre-commit && pre-commit install",".",[663,694,696],{"question":695},"Will secret scanning catch secrets in .env files?",[10,697,698],{},"Only if the .env file is tracked by git. If it is in .gitignore, scanners won't see it because git doesn't track it. The risk is when .env files are accidentally committed. The scanner catches that. Keep .env in .gitignore and commit only .env.example with placeholder values.",[700,701,702,708,713],"related-articles",{},[703,704],"related-card",{"description":705,"href":706,"title":707},"Set up automatic secret detection with GitHub secret scanning, pre-commit hooks, and CI/CD pipelines.","/blog/how-to/secret-scanning","How to Enable Secret Scanning",[703,709],{"description":710,"href":711,"title":712},"Clean secrets from git history using BFG Repo Cleaner after an accidental commit.","/blog/how-to/remove-secrets-git-history","How to Remove Secrets from Git History",[703,714],{"description":715,"href":716,"title":717},"What happens when Stripe, OpenAI, or AWS keys end up in public code.","/blog/vulnerabilities/exposed-api-keys-explained","Exposed API Keys: What They Are and Why They're Dangerous",[719,720,723,727],"cta-box",{"href":721,"label":722},"/","Start Free Scan",[28,724,726],{"id":725},"secrets-in-your-deployed-app","Secrets in your deployed app?",[10,728,729],{},"Scanning your git repo catches what's in version control. CheckYourVibe scans your live app for API keys exposed in JavaScript, misconfigured headers, and other runtime security issues.",[731,732,733],"style",{},"html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":70,"searchDepth":165,"depth":165,"links":735},[736,737,741,744,748,751,754,755,756],{"id":30,"depth":165,"text":31},{"id":50,"depth":165,"text":51,"children":738},[739,740],{"id":59,"depth":181,"text":60},{"id":76,"depth":181,"text":77},{"id":89,"depth":165,"text":90,"children":742},[743],{"id":99,"depth":181,"text":100},{"id":118,"depth":165,"text":119,"children":745},[746,747],{"id":128,"depth":181,"text":129},{"id":255,"depth":181,"text":256},{"id":280,"depth":165,"text":281,"children":749},[750],{"id":290,"depth":181,"text":291},{"id":322,"depth":165,"text":323,"children":752},[753],{"id":332,"depth":181,"text":333},{"id":491,"depth":165,"text":492},{"id":620,"depth":165,"text":621},{"id":725,"depth":165,"text":726},"how-to","2026-05-12","How to scan your git repo for exposed API keys, passwords, and credentials. Covers gitleaks, detect-secrets, pre-commit hooks, history scanning, and CI integration.",false,"md",[763,764,766,767,769],{"question":665,"answer":668},{"question":671,"answer":765},"Run gitleaks git --source . to walk every commit in the current repository. This scans the full history, not just the current files. If secrets are found in old commits, rotate the credentials immediately. They are already public, even if the file has since been deleted.",{"question":679,"answer":682},{"question":685,"answer":768},"Use a pre-commit hook that runs gitleaks before each commit. The hook blocks the commit if a secret is detected. Wire it via the pre-commit framework so all teammates get it automatically when they run `pip install pre-commit && pre-commit install`.",{"question":695,"answer":698},"yellow",null,"detect secrets in git, scan git repo for secrets, gitleaks, detect-secrets, git secret scanning, exposed credentials git, pre-commit secret scanning",{},"Scan your git repo for leaked secrets with gitleaks and detect-secrets. Covers history scanning, pre-commit hooks, and CI/CD integration.","/blog/how-to/detect-secrets-in-git","9 min read","[object Object]","HowTo",{"title":5,"description":759},{"loc":775},"blog/how-to/detect-secrets-in-git",[],"summary_large_image","jwwjz2Ezo0pAgd-AO2XFGMGofTnV9pXis4Pd0b2BzD8",1778607041583]