How to Fix a Leaked API Key: A Developer’s Guide to Git Security — Opportunihub
Course Remote

How to Fix a Leaked API Key: A Developer’s Guide to Git Security

Eva J Patel · Remote

At a glance

Type
Course
Organisation
Eva J Patel
Location
Remote
Work mode
Remote
Deadline
Rolling / not stated
Posted
25 Aug 2026

About this course

<p>Imagine this: you're working late, your code finally works, and you're ready to push it to GitHub.</p> <p>You run:</p> <pre><code class="language-bash">git add . git commit -m "Fix API integration" git push </code></pre> <p>A few minutes later, you notice something strange. Your API usage has suddenly increased. Maybe there are unexpected requests, new cloud resources, or even a bill that looks much larger than expected.</p> <p>Then you find it:</p> <pre><code class="language-javascript">const apiKey = "sk_live_123456789"; </code></pre> <p>Your API key is sitting in a Git repository.</p> <p>This situation is stressful, but it's fixable.</p> <p>The most important rule is:</p> <blockquote> <p><strong>If an API key has been committed to Git, assume it has been copied and compromised, even if you delete it immediately.</strong></p> </blockquote> <p>Deleting the key from the latest version of your file doesn't make the old key safe. Git keeps previous versions of files in its history, and exposed credentials can be discovered by automated scanners.</p> <p>In this guide, you'll learn the following:</p> <ul> <li><p><a href="#heading-what-is-an-api-key">What Is an API Key?</a></p> </li> <li><p><a href="#heading-the-emergency-response-what-to-do-first">The Emergency Response: What to Do First</a></p> </li> <li><p><a href="#heading-step-1-revoke-or-rotate-the-leaked-key">Step 1: Revoke or Rotate the Leaked Key</a></p> </li> <li><p><a href="#heading-step-2-investigate-suspicious-activity">Step 2: Investigate Suspicious Activity</a></p> </li> <li><p><a href="#heading-step-3-remove-the-secret-from-your-current-code">Step 3: Remove the Secret From Your Current Code</a></p> </li> <li><p><a href="#heading-step-4-use-a-env-file-for-local-development">Step 4: Use a.envFile for Local Development</a></p> </li> <li><p><a href="#heading-step-5-create-a-safe-envexample">Step 5: Create a Safe.env.example</a></p> </li> <li><p><a href="#heading-step-6-determine-whether-the-secret-is-still-in-git-history">Step 6: Determine Whether the Secret Is Still in Git History</a></p> </li> <li><p><a href="#heading-when-do-you-need-to-rewrite-git-history">When Do You Need to Rewrite Git History?</a></p> </li> <li><p><a href="#heading-step-7-remove-the-secret-from-git-history">Step 7: Remove the Secret From Git History</a></p> </li> <li><p><a href="#heading-step-8-verify-that-the-secret-is-gone">Step 8: Verify That the Secret Is Gone</a></p> </li> <li><p><a href="#heading-step-9-push-the-cleaned-history-carefully">Step 9: Push the Cleaned History Carefully</a></p> </li> <li><p><a href="#heading-step-10-replace-the-credential-everywhere">Step 10: Replace the Credential Everywhere</a></p> </li> <li><p><a href="#heading-step-11-restrict-the-replacement-key">Step 11: Restrict the Replacement Key</a></p> </li> <li><p><a href="#heading-what-about-frontend-applications">What About Frontend Applications?</a></p> </li> <li><p><a href="#heading-environment-variables-vs-secret-managers">Environment Variables vs Secret Managers</a></p> </li> <li><p><a href="#heading-add-secret-scanning-to-your-workflow">Add Secret Scanning to Your Workflow</a></p> </li> <li><p><a href="#heading-use-git-hooks-as-an-extra-safety-net">Use Git Hooks as an Extra Safety Net</a></p> </li> <li><p><a href="#heading-review-your-staged-diff-before-committing">Review Your Staged Diff Before Committing</a></p> </li> <li><p><a href="#heading-common-mistakes-developers-make">Common Mistakes Developers Make</a></p> </li> <li><p><a href="#heading-a-complete-api-key-incident-checklist">A Complete API-Key Incident Checklist</a></p> </li> <li><p><a href="#heading-a-secure-project-structure">A Secure Project Structure</a></p> </li> </ul> <p>We'll use this basic workflow throughout the article:</p> <pre><code class="language-text">Invalidate → Investigate → Remove → Replace → Prevent </code></pre> <p>Let's start with what an API key actually is before we get to the most important part: what to do <strong>right now</strong> after a key is exposed.</p> <h2 id="heading-what-is-an-api-key">What Is an API Key?</h2> <p>An API key is a credential that allows an application to communicate with another service.</p> <p>For example, an application might use an API key to access:</p> <ul> <li><p>A weather service</p> </li> <li><p>A payment provider</p> </li> <li><p>A mapping service</p> </li> <li><p>An artificial intelligence API</p> </li> <li><p>A cloud platform</p> </li> <li><p>A database</p> </li> <li><p>An email provider</p> </li> <li><p>A private company API</p> </li> </ul> <p>A key might look something like this:</p> <pre><code class="language-javascript">const apiKey = "your-real-api-key"; </code></pre> <p>Or it might appear in a configuration file:</p> <pre><code class="language-json">{ "apiKey": "your-real-api-key", "databasePassword": "your-real-password" } </code></pre> <p>API keys are often called <strong>secrets</strong> because possessing one may allow someone to make requests, access data, create resources, or generate charges on your account.</p> <p>Not every API key is equally sensitive. Some services provide browser keys that are intentionally visible to users. Those keys should still have appropriate restrictions, quotas, and permissions.</p> <p>As a general rule:</p> <blockquote> <p><strong>If a credential can access private data, create resources, modify records, or generate charges, it shouldn't be stored directly in your source code.</strong></p> </blockquote> <h2 id="heading-the-emergency-response-what-to-do-first">The Emergency Response: What to Do First</h2> <p>When you discover a leaked credential, a common reaction is to delete the key from the file and push another commit.</p> <p>Don't start there.</p> <p>Your first priority is to <strong>make the leaked credential useless</strong>.</p> <p>Use this order of operations:</p> <pre><code class="language-text">1. Invalidate the leaked credential 2. Investigate suspicious activity 3. Remove the secret from your code 4. Replace it with a new credential 5. Clean the Git history if necessary 6. Verify the cleanup 7. Add protections against future leaks </code></pre> <p>Think of an API key like a house key that was dropped in a crowded street.</p> <p>Deleting a picture of the key doesn't matter if someone already picked up the physical key.</p> <p><strong>Change the lock first.</strong></p> <h2 id="heading-step-1-revoke-or-rotate-the-leaked-key">Step 1: Revoke or Rotate the Leaked Key</h2> <p>Go to the dashboard of the service that issued the credential.</p> <p>Depending on the provider, you may see options such as:</p> <ul> <li><p>Revoke</p> </li> <li><p>Delete</p> </li> <li><p>Disable</p> </li> <li><p>Rotate</p> </li> <li><p>Regenerate</p> </li> <li><p>Create new key</p> </li> </ul> <p>If the provider supports key rotation, create a replacement credential before disabling the old one if possible. This can reduce application downtime while you update your configuration.</p> <p>The important thing is that the original credential must no longer be usable.</p> <p><strong>Do not reuse the leaked key.</strong> Don't rename it. Don't encode it. Don't move it to another file and assume it is safe. Don't assume nobody saw it.</p> <p>Treat it as compromised.</p> <h2 id="heading-step-2-investigate-suspicious-activity">Step 2: Investigate Suspicious Activity</h2> <p>After disabling the credential, check the provider's usage dashboard and logs.</p> <p>Look for things such as:</p> <ul> <li><p>Sudden spikes in requests</p> </li> <li><p>Requests from unfamiliar locations</p> </li> <li><p>Unexpected database queries</p> </li> <li><p>New cloud resources</p> </li> <li><p>Changes to permissions</p> </li> <li><p>Unexpected downloads</p> </li> <li><p>Unusual payment activity</p> </li> <li><p>New deployments</p> </li> <li><p>Requests at times when your application was inactive</p> </li> </ul> <p>If the credential had broad permissions, assume that anything within its permission scope <strong>MAY have been accessed or modified</strong>.</p> <p>For example, if a cloud credential could create virtual machines, check whether unexpected machines were created.</p> <p>If a credential could access a database, review:</p> <ul> <li><p>Authentication logs</p> </li> <li><p>Read operations</p> </li> <li><p>Write operations</p> </li> <li><p>Deleted records</p> </li> <li><p>Exported data</p> </li> <li><p>Newly created accounts</p> </li> <li><p>Permission changes</p> </li> </ul> <p>Also check your billing information if the credential could generate usage-based charges.</p> <p>Write down what you discover. A simple timeline can help:</p> <pre><code class="language-text">10:15 - API key committed 10:23 - Repository pushed publicly 10:41 - Unusual usage detected 10:45 - Key revoked 11:00 - Logs reviewed 11:30 - Replacement key deployed 12:00 - Git history cleaned </code></pre> <p>This can be especially useful if you need to report the incident to a team or service provider.</p> <h2 id="heading-step-3-remove-the-secret-from-your-current-code">Step 3: Remove the Secret From Your Current Code</h2> <p>Once the original credential has been disabled, remove it from your working files.</p> <p>This is unsafe:</p> <pre><code class="language-javascript">const apiKey = "your-real-api-key"; </code></pre> <p>Instead, load the credential from the environment:</p> <pre><code class="language-javascript">const apiKey = process.env.API_KEY; if (!apiKey) { throw new Error("API_KEY is not configured"); } </code></pre> <p>In Python:</p> <pre><code class="language-python">import os api_key = os.environ.get("API_KEY") if not api_key: raise RuntimeError("API_KEY is not configured") </code></pre> <p>The important idea is simple:</p> <pre><code class="language-text">Source code → environment variable → secret value </code></pre> <p>instead of:</p> <pre><code class="language-text">Source code → hardcoded secret </code></pre> <p>Environment variables aren't the only way to manage secrets, but they are a common and practical solution for local development and many deployment environments.</p> <h2 id="heading-step-4-use-a-env-file-for-local-development">Step 4: Use a <code>.env</code> File for Local Development</h2> <p>For local development, you can store environment variables in a <code>.env</code> file.</p> <p>For example:</p> <pre><code class="language-env">API_KEY=your-local-development-key DATABASE_URL=your-local-database-url </code></pre> <p>A Node.js project can load these values with a package such as <code>dotenv</code>.</p> <p>Install it with:</p> <pre><code class="language-bash">npm install dotenv </code></pre> <p>Then:</p> <pre><code class="language-javascript">import "dotenv/config"; const apiKey = process.env.API_KEY; </code></pre> <p>The important part is that the <code>.env</code> file normally <strong>should not be committed to Git</strong>.</p> <p>Add it to <code>.gitignore</code>:</p> <pre><code class="language-gitignore"># Environment files .env .env.* !.env.example # Credential files *.pem *.key credentials.json service-account.json # Local development files .DS_Store </code></pre> <p>But there's an important detail here: the <code>.gitignore</code> <strong>does NOT remove files that Git is already tracking.</strong></p> <p>If <code>.env</code> has already been committed, adding it to <code>.gitignore</code> won't erase it from Git.</p> <p>You can stop tracking the file while keeping it on your computer:</p> <pre><code class="language-bash">git rm --cached .env </code></pre> <p>Then commit the <code>.gitignore</code> change:</p> <pre><code class="language-bash">git add .gitignore git commit -m "Ignore local environment files" </code></pre> <p>But remember: this only removes the file from future commits. It does <strong>not</strong> remove the secret from previous commits.</p> <p>That's where Git history comes in.</p> <h2 id="heading-step-5-create-a-safe-envexample">Step 5: Create a Safe <code>.env.example</code></h2> <p>Other developers still need to know which environment variables the application requires.</p> <p>Instead of committing <code>.env</code>, create <code>.env.example</code>:</p> <pre><code class="language-env">API_KEY= DATABASE_URL= PORT=3000 LOG_LEVEL=info </code></pre> <p>This file contains variable names rather than real credentials, so it can be committed to the repository.</p> <p>You can also provide comments:</p> <pre><code class="language-env"># Required API credential API_KEY= # PostgreSQL connection string DATABASE_URL= # Optional application port PORT=3000 </code></pre> <p>A new developer can then copy the file:</p> <pre><code class="language-bash">cp .env.example .env </code></pre> <p>and provide their own values.</p> <p>Use clearly fake placeholders in examples:</p> <pre><code class="language-env">API_KEY=replace-me-with-your-own-key </code></pre> <p>Avoid putting realistic-looking production credentials into <code>.env.example</code>.</p> <h2 id="heading-step-6-determine-whether-the-secret-is-still-in-git-history">Step 6: Determine Whether the Secret Is Still in Git History</h2> <p>This is one of the most important parts of fixing a leaked credential.</p> <p>Suppose your Git history looks like this:</p> <pre><code class="language-text">Commit A: Add API key to config.js Commit B: Update API integration Commit C: Delete API key </code></pre> <p>Even though Commit C no longer contains the key, Commit A still does.</p> <p>Git remembers previous versions of your files.</p> <p>You can inspect the history of a file with:</p> <pre><code class="language-bash">git log --all -- config.js </code></pre> <p>To display a file from an older commit:</p> <pre><code class="language-bash">git show COMMIT_ID:config.js </code></pre> <p>You can also search Git history for a known leaked value:</p> <pre><code class="language-bash">git log --all -S"your-leaked-key" --oneline </code></pre> <p>If you know the secret was committed, you should assume that it exists somewhere in the repository's history until you've verified otherwise.</p> <h2 id="heading-when-do-you-need-to-rewrite-git-history">When Do You Need to Rewrite Git History?</h2> <p>Not every accidental secret requires a history rewrite. Consider these situations:</p> <h3 id="heading-the-secret-was-never-committed">The Secret Was Never Committed</h3> <p>If the secret exists only in your working directory and was never committed, you generally don't need to rewrite history.</p> <p>Remove it, add the appropriate file to <code>.gitignore</code>, and continue.</p> <h3 id="heading-the-secret-was-committed-locally-but-never-pushed">The Secret Was Committed Locally But Never Pushed</h3> <p>If the secret exists in local commits but hasn't been shared with a remote repository, you may be able to clean up those commits before pushing.</p> <h3 id="heading-the-secret-was-pushed-to-a-remote-repository">The Secret Was Pushed to a Remote Repository</h3> <p>Treat the credential as compromised. Revoke or rotate it immediately.</p> <p>Then determine whether removing the secret from the repository's history is appropriate.</p> <h3 id="heading-the-repository-was-public">The Repository Was Public</h3> <p>Assume that someone or something may already have copied the secret.</p> <p>This is why <strong>revocation comes before Git cleanup</strong>.</p> <h3 id="heading-the-secret-was-in-a-private-repository">The Secret Was in a Private Repository</h3> <p>A private repository is safer than a public repository, but it isn't a secret vault.</p> <p>Credentials can still escape through:</p> <ul> <li><p>Compromised accounts</p> </li> <li><p>Contractors</p> </li> <li><p>Integrations</p> </li> <li><p>CI logs</p> </li> <li><p>Forks</p> </li> <li><p>Backups</p> </li> <li><p>Screenshots</p> </li> <li><p>Copied code</p> </li> <li><p>Pull requests</p> </li> </ul> <p>So the safest rule remains:</p> <blockquote> <p><strong>Never intentionally commit credentials to Git, even in a private repository.</strong></p> </blockquote> <h2 id="heading-step-7-remove-the-secret-from-git-history">Step 7: Remove the Secret From Git History</h2> <p>If the credential was committed, you may need to remove it from the repository's history.</p> <p>Before rewriting history, create a backup:</p> <pre><code class="language-bash">git clone --mirror https://github.com/your-username/your-repository.git repository-backup.git </code></pre> <p>A mirror clone includes branches and tags, which makes it useful for recovery if something goes wrong.</p> <h3 id="heading-option-1-remove-an-entire-file">Option 1: Remove an Entire File</h3> <p>If the secret was stored in a file such as <code>.env</code>, you can remove that file from the entire history:</p> <pre><code class="language-bash">git filter-repo --path .env --invert-paths </code></pre> <p>For a file inside a directory:</p> <pre><code class="language-bash">git filter-repo --path config/production.json --invert-paths </code></pre> <p>This removes the file from the repository's rewritten history.</p> <h3 id="heading-option-2-replace-a-secret-inside-a-file">Option 2: Replace a Secret Inside a File</h3> <p>Sometimes you need to keep the file but remove the secret from previous versions.</p> <p>Create a temporary replacements file:</p> <p>Then run:</p> <pre><code class="language-bash">git filter-repo --replace-text replacements.txt </code></pre> <p>You can replace the value with a placeholder:</p> <pre><code class="language-text">your-leaked-key==&gt;YOUR_API_KEY_HERE </code></pre> <p>Be extremely careful with <code>replacements.txt</code>. It contains the original secret, so <strong>do not commit it.</strong></p> <p>Delete it after the cleanup:</p> <pre><code class="language-bash">rm replacements.txt </code></pre> <p>On Windows PowerShell:</p> <pre><code class="language-powershell">Remove-Item replacements.txt </code></pre> <p>For multiple secrets:</p> <pre><code class="language-text">old-api-key==&gt;REMOVED_API_KEY old-database-password==&gt;REMOVED_DATABASE_PASSWORD old-token==&gt;REMOVED_TOKEN </code></pre> <p>Then:</p> <pre><code class="language-bash">git filter-repo --replace-text replacements.txt </code></pre> <p>Test the cleanup on your backup clone first.</p> <h2 id="heading-step-8-verify-that-the-secret-is-gone">Step 8: Verify That the Secret Is Gone</h2> <p>Never assume the cleanup worked just because the command completed successfully.</p> <p>Search for the known leaked value again:</p> <pre><code class="language-bash">git log --all -S"your-leaked-key" --oneline </code></pre> <p>You can also inspect relevant files and commits:</p> <pre><code class="language-bash">git log --all -- config.js </code></pre> <p>and:</p> <pre><code class="language-bash">git show COMMIT_ID:config.js </code></pre> <p>If your repository uses branches and tags, make sure you aren't checking only the branch you currently have checked out.</p> <p>You should also inspect other locations where the secret may have appeared, including pull requests, CI/CD logs, build artifacts, release files, Docker images, package releases, documentation, issue comments, and screenshots</p> <p>Remember:</p> <blockquote> <p><strong>Rewriting your repository doesn't erase copies that already exist somewhere else.</strong></p> </blockquote> <p>That's another reason why the original credential must be revoked.</p> <h2 id="heading-step-9-push-the-cleaned-history-carefully">Step 9: Push the Cleaned History Carefully</h2> <p>Once you've verified the cleanup, you may need to push the rewritten history:</p> <pre><code class="language-bash">git push --force --all origin git push --force --tags origin </code></pre> <h3 id="heading-important-warning">Important Warning</h3> <p><strong>Force-pushing rewritten history is disruptive.</strong> It changes commit hashes and can affect collaborators who have existing clones of the repository.</p> <p>Before doing this on a shared project:</p> <ol> <li><p>Tell your collaborators.</p> </li> <li><p>Make sure everyone understands that history is being rewritten.</p> </li> <li><p>Coordinate the cleanup.</p> </li> <li><p>Follow your organization's incident-response process if one exists.</p> </li> </ol> <p>After the rewrite, collaborators may need to reclone the repository:</p> <pre><code class="language-bash">git clone https://github.com/your-username/your-repository.git </code></pre> <p>They shouldn't blindly merge their old repository history back into the cleaned repository.</p> <h2 id="heading-step-10-replace-the-credential-everywhere">Step 10: Replace the Credential Everywhere</h2> <p>Now create or use the replacement credential. Update every environment where the application runs. Common locations include:</p> <ul> <li><p>Local development</p> </li> <li><p>Testing</p> </li> <li><p>Staging</p> </li> <li><p>Production</p> </li> <li><p>Docker containers</p> </li> <li><p>Kubernetes secrets</p> </li> <li><p>CI/CD systems</p> </li> <li><p>Hosting platforms</p> </li> <li><p>Scheduled jobs</p> </li> <li><p>Serverless functions</p> </li> </ul> <p>A common mistake is updating production but forgetting the deployment pipeline.</p> <p>For example, your local application may work because <code>.env</code> contains the new key, while your CI/CD system still contains the old one.</p> <p>Make a checklist:</p> <pre><code class="language-text">1. Local development 2. Automated tests 3. Staging 4. Production 5. CI/CD variables 6. Docker configuration 7. Cloud deployment settings 8. Scheduled scripts 9. Serverless functions </code></pre> <p>After updating the credential, test the application in each important environment.</p> <h2 id="heading-step-11-restrict-the-replacement-key">Step 11: Restrict the Replacement Key</h2> <p>Replacing a leaked credential is only part of the solution.</p> <p>The new credential should have <strong>only the permissions it actually needs</strong>.</p> <p>Useful restrictions can include:</p> <ul> <li><p>Read-only permissions</p> </li> <li><p>Specific API scopes</p> </li> <li><p>Allowed IP addresses</p> </li> <li><p>Allowed domains</p> </li> <li><p>Environment-specific access</p> </li> <li><p>Request quotas</p> </li> <li><p>Rate limits</p> </li> <li><p>Expiration dates</p> </li> </ul> <p>For example, a weather application may only need permission to read weather data.</p> <p>It shouldn't have permission to manage users, modify billing, or delete unrelated resources.</p> <p>This is the <strong>principle of least privilege</strong>:</p> <blockquote> <p><strong>Give each credential the smallest amount of access necessary to perform its job.</strong></p> </blockquote> <p>It's also a good idea to use different credentials for different environments:</p> <pre><code class="language-text">local-development-key testing-key staging-key production-key </code></pre> <p>That way, a development credential leak doesn't automatically expose production resources.</p> <h2 id="heading-what-about-frontend-applications">What About Frontend Applications?</h2> <p>This is where API-key security gets confusing.</p> <p>Frontend code runs on the user's device.</p> <p>That means users can inspect it.</p> <p>For example:</p> <pre><code class="language-javascript">const apiKey = "browser-key"; </code></pre> <p>A user can inspect the JavaScript bundle, browser developer tools, or network requests and potentially see the value.</p> <p>Some services intentionally provide browser API keys that are designed to be publicly visible.</p> <p>Those keys should still be restricted by things such as:</p> <ul> <li><p>Allowed domains</p> </li> <li><p>Website origins</p> </li> <li><p>API operations</p> </li> <li><p>Usage quotas</p> </li> <li><p>Referrer restrictions</p> </li> <li><p>Time limits</p> </li> </ul> <p>But a truly private credential should <strong>never be placed in browser code</strong>.</p> <p>Instead of:</p> <pre><code class="language-javascript">fetch("https://private-api.example.com/data", { headers: { Authorization: "Bearer private-secret-token" } }); </code></pre> <p>have the browser call your own backend:</p> <pre><code class="language-javascript">fetch("/api/data"); </code></pre> <p>Then the backend communicates with the private service:</p> <pre><code class="language-javascript">const response = await fetch( "https://private-api.example.com/data", { headers: { Authorization: `Bearer ${process.env.PRIVATE_API_TOKEN}` } } ); </code></pre> <p>The backend can then return only the information the browser is allowed to receive.</p> <p>The important distinction is:</p> <pre><code class="language-text">Public/browser credential ↓ Can be visible, but should be restricted Private credential ↓ Must remain on a trusted backend or secret-management system </code></pre> <h2 id="heading-environment-variables-vs-secret-managers">Environment Variables vs Secret Managers</h2> <p>Environment variables are useful, but they're not a universal secret-management solution.</p> <p>For a small application or local development environment, something like:</p> <pre><code class="language-env">API_KEY=your-secret </code></pre> <p>may be perfectly reasonable.</p> <p>For larger production systems, you may want a dedicated <strong>secret manager</strong>.</p> <p>A secret-management system can provide features such as:</p> <ul> <li><p>Centralized credential storage</p> </li> <li><p>Access controls</p> </li> <li><p>Auditing</p> </li> <li><p>Credential rotation</p> </li> <li><p>Versioning</p> </li> <li><p>Separation between environments</p> </li> <li><p>Integration with deployment systems</p> </li> </ul> <p>The important idea is that your source code shouldn't be responsible for storing production secrets.</p> <p>Instead:</p> <pre><code class="language-text">Application ↓ Secret management system ↓ Credential </code></pre> <p>rather than:</p> <pre><code class="language-text">Application ↓ Hardcoded production credential </code></pre> <p>Which solution you use depends on the size and requirements of your project.</p> <h2 id="heading-add-secret-scanning-to-your-workflow">Add Secret Scanning to Your Workflow</h2> <p>Humans are excellent programmers and occasionally terrible search engines.</p> <p>Automated secret scanning can catch credentials before they make it into a repository.</p> <p>Popular tools include:</p> <ul> <li><p>Gitleaks</p> </li> <li><p>TruffleHog</p> </li> <li><p>detect-secrets</p> </li> <li><p>Pre-commit hooks</p> </li> <li><p>Git hosting secret scanning</p> </li> <li><p>CI security scanners</p> </li> </ul> <p>For example, you can run Gitleaks locally:</p> <pre><code class="language-bash">gitleaks detect --source . --verbose </code></pre> <p>You can also integrate secret scanning into CI.</p> <p>A basic GitHub Actions workflow might look like this:</p> <pre><code class="language-yaml">name: Secret Scan on: push: pull_request: jobs: scan: runs-on: ubuntu-latest steps: - name: Check out repository uses: actions/checkout@v4 with: fetch-depth: 0 - name: Scan for secrets uses: gitleaks/gitleaks-action@v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} </code></pre> <p>Review the documentation for your chosen tool and pin versions according to your project's security practices.</p> <p>Secret scanners can produce false positives, so you may need to configure exceptions for safe test values.</p> <p>Be careful with allowlists, though. An overly broad exception can hide a real credential.</p> <h2 id="heading-use-git-hooks-as-an-extra-safety-net">Use Git Hooks as an Extra Safety Net</h2> <p>You can also scan files before they're committed.</p> <p>For example, a simple pre-commit script could search for suspicious words:</p> <pre><code class="language-bash">#!/usr/bin/env bash if grep -RniE "api[_-]?key|password|secret|token|private[_-]?key" . \ --exclude-dir=.git \ --exclude=".env.example"; then echo "Possible secret detected. Commit cancelled." exit 1 fi </code></pre> <p>This isn't a complete security scanner, but it can catch obvious mistakes.</p> <p>For stronger protection, use a dedicated secret-scanning tool through a pre-commit framework.</p> <p>The goal isn't to make committing miserable. The goal is to make accidentally publishing a credential harder.</p> <h2 id="heading-review-your-staged-diff-before-committing">Review Your Staged Diff Before Committing</h2> <p>One of the simplest security habits you can develop is checking what you're actually about to commit.</p> <p>First:</p> <pre><code class="language-bash">git status </code></pre> <p>Then stage only the files you intend to commit:</p> <pre><code class="language-bash">git add src/api.js README.md </code></pre> <p>Now inspect the staged changes:</p> <pre><code class="language-bash">git diff --cached </code></pre> <p>Look for:</p> <ul> <li><p>API keys</p> </li> <li><p>Passwords</p> </li> <li><p>Tokens</p> </li> <li><p>Private URLs</p> </li> <li><p>Internal hostnames</p> </li> <li><p>Customer data</p> </li> <li><p>Debug output</p> </li> <li><p>Personal information</p> </li> <li><p>Private certificates</p> </li> </ul> <p>Only commit after the staged diff looks correct:</p> <pre><code class="language-bash">git commit -m "Load API key from environment" </code></pre> <p>Be cautious with:</p> <pre><code class="language-bash">git add . </code></pre> <p>It can stage files you never intended to publish, including <code>.env</code> files, database exports, generated files, or local configuration.</p> <h2 id="heading-common-mistakes-developers-make">Common Mistakes Developers Make</h2> <h3 id="heading-mistake-1-i-deleted-it-so-its-fine">Mistake 1: "I Deleted It, So It's Fine"</h3> <p>Deleting a secret from the current version of a file doesn't delete it from Git history.</p> <p><strong>Correct response:</strong> Revoke the credential and clean the repository history when appropriate.</p> <h3 id="heading-mistake-2-the-repository-is-private">Mistake 2: "The Repository Is Private"</h3> <p>Private repositories aren't vaults.</p> <p>Credentials can still escape through compromised accounts, integrations, CI logs, forks, backups, or copied code.</p> <p><strong>Correct response:</strong> Don't commit secrets even to private repositories.</p> <h3 id="heading-mistake-3-ill-just-encode-it">Mistake 3: "I'll Just Encode It"</h3> <p>These don't make a credential secret:</p> <pre><code class="language-javascript">const key = atob("c29tZS1rZXk="); </code></pre> <p>or:</p> <pre><code class="language-javascript">const key = "some-" + "secret-" + "value"; </code></pre> <p>Encoding, splitting, renaming, or hiding a credential doesn't protect it.</p> <p>If your application can reconstruct the credential, someone analyzing the application may be able to do the same.</p> <h3 id="heading-mistake-4-logging-the-secret">Mistake 4: Logging the Secret</h3> <p>Don't do this:</p> <pre><code class="language-javascript">console.log(process.env.API_KEY); </code></pre> <p>Logs can be stored by your terminal, CI system, hosting provider, monitoring platform, or cloud service.</p> <p>Instead:</p> <pre><code class="language-javascript">console.log( "API key configured:", Boolean(process.env.API_KEY) ); </code></pre> <p>If you absolutely need to inspect a value during debugging, avoid printing the full credential.</p> <p>For example:</p> <pre><code class="language-javascript">function maskSecret(value) { if (!value) return "not configured"; if (value.length &lt;= 8) return "********"; return `${value.slice(0, 4)}...${value.slice(-4)}`; } console.log(maskSecret(process.env.API_KEY)); </code></pre> <p>Even masked credentials should be handled carefully.</p> <h3 id="heading-mistake-5-using-the-same-credential-everywhere">Mistake 5: Using the Same Credential Everywhere</h3> <p>If local development, testing, staging, and production all use the same credential, one leak can affect everything.</p> <p><strong>Correct response:</strong> Use separate credentials with separate permissions.</p> <h3 id="heading-mistake-6-cleaning-only-the-current-branch">Mistake 6: Cleaning Only the Current Branch</h3> <p>A secret can remain in:</p> <ul> <li><p>Old branches</p> </li> <li><p>Tags</p> </li> <li><p>Pull requests</p> </li> <li><p>Other references</p> </li> </ul> <p><strong>Correct response:</strong> Consider the entire repository when investigating and cleaning a leaked credential.</p> <h3 id="heading-mistake-7-forgetting-build-artifacts">Mistake 7: Forgetting Build Artifacts</h3> <p>A secret might also appear in:</p> <ul> <li><p>Compiled JavaScript bundles</p> </li> <li><p>Docker images</p> </li> <li><p>Downloadable releases</p> </li> <li><p>Published packages</p> </li> <li><p>Generated documentation</p> </li> </ul> <p><strong>Correct response:</strong> Revoke the credential and identify affected artifacts that may need to be removed or replaced.</p> <h2 id="heading-a-complete-api-key-incident-checklist">A Complete API-Key Incident Checklist</h2> <p>If you discover that you've exposed an API key, use this checklist:</p> <pre><code class="language-text">1. Revoke or rotate the leaked key 2. Create a replacement credential 3. Restrict the replacement credential 4. Review provider logs 5. Review billing and usage 6. Check for unauthorized resources 7. Remove the key from current files 8. Add secret files to .gitignore 9. Create or update .env.example 10. Search Git history 11. Check branches and tags 12. Remove the secret from Git history if necessary 13. Verify the old secret is gone 14. Force-push cleaned history if appropriate 15. Check pull requests and forks 16. Check CI and deployment logs 17. Update local configuration 18. Update staging configuration 19. Update production configuration 20. Update CI/CD secrets 21. Run a secret scanner 22. Document the incident 23. Add preventive security checks </code></pre> <p>The exact steps will depend on your provider and project, but the order matters: <strong>Invalidate first. Clean up second.</strong></p> <h2 id="heading-a-secure-project-structure">A Secure Project Structure</h2> <p>A simple Node.js project might look like this:</p> <pre><code class="language-text">my-project/ ├── src/ │ └── api.js ├── .env ├── .env.example ├── .gitignore ├── package.json └── README.md </code></pre> <p>The local <code>.env</code> file contains the actual development value:</p> <pre><code class="language-env">API_KEY=your-local-key </code></pre> <p>The <code>.env.example</code> file contains no real credential:</p> <pre><code class="language-env">API_KEY=replace-me-with-your-own-key </code></pre> <p>The application reads the environment variable:</p> <pre><code class="language-javascript">import "dotenv/config"; const apiKey = process.env.API_KEY; if (!apiKey) { throw new Error("Missing API_KEY environment variable"); } export async function getData() { const response = await fetch( "https://api.example.com/data", { headers: { Authorization: `Bearer ${apiKey}` } } ); if (!response.ok) { throw new Error( `API request failed: ${response.status}` ); } return response.json(); } </code></pre> <p>And <code>.gitignore</code> keeps the local environment file out of future commits:</p> <pre><code class="language-gitignore">.env .env.* !.env.example node_modules/ </code></pre> <p>Finally, your README can explain the setup without exposing credentials:</p> <p>Step 1: Copy the example environment file on your bash <code>cp .env.example .env</code></p> <p>Step 2: Add your own API key to <code>.env</code>.</p> <p>And last but not least, start your application!</p> <pre><code class="language-bash">npm start </code></pre> <h2 id="heading-final-thoughts">Final Thoughts</h2> <p>Leaking an API key doesn't mean you're a terrible developer. It just means your development workflow needs better guardrails.</p> <p>The important thing is knowing how to respond quickly and how to prevent the same mistake from happening again.</p> <p>Remember the emergency formula:</p> <pre><code class="language-text">Invalidate → Investigate → Remove → Replace → Prevent </code></pre> <p>The important thing is knowing how to respond quickly and how to prevent the same mistake from happening again.</p> <ul> <li><p>Invalidate the leaked credential so it can no longer be used.</p> </li> <li><p>Investigate your logs, usage, and billing to determine whether it was abused.</p> </li> <li><p>Remove the secret from your current code and, when necessary, from Git history.</p> </li> <li><p>Replace it with a new credential that has only the permissions it needs.</p> </li> <li><p>Prevent future leaks with environment variables, secret managers, secret scanning, and careful Git practices.</p> </li> </ul> <p>Git is excellent at remembering your project's history. That's useful when you accidentally delete an important function. But it's much less useful when that history contains a password.</p> <p>So keep your code public when appropriate. And <strong>keep your secrets somewhere else.</strong></p> <p>Happy coding!</p>

How to apply

  1. 1 Read the full details above and confirm you meet the eligibility criteria.
  2. 2 Prepare your documents — an updated CV, and any cover letter, proposal or certificates required.
  3. 3 Click Apply on official site to complete your application on Eva J Patel’s official page.
  4. 4 Submit as early as possible — many close once filled.
Apply on official site

Sourced from freecodecamp. Always verify details on the official website. Opportunihub never charges you to apply.

Frequently asked questions

How do I apply for How to Fix a Leaked API Key: A Developer’s Guide to Git Security?

Review the full details and eligibility on this page, prepare your documents, then use the “Apply on official site” button to complete your application on Eva J Patel’s official page.

Is this opportunity remote or location-based?

This opportunity is remote-friendly and open to applicants who can work from anywhere.

Is How to Fix a Leaked API Key: A Developer’s Guide to Git Security free to apply for?

Opportunihub lists this Course for free. Legitimate Courses do not ask for payment to apply — never pay a fee to submit an application.