Least-privilege API keys and secrets for your automations
How to scope, store, rotate and revoke the API keys your scripts, workflows and AI agents use, based on OWASP, GitHub, AWS and Google Cloud guidance.
Most automations start the same way. Someone needs a script to read a spreadsheet, post to Slack or update the CRM, so they create an API key with full admin rights, paste it into a config file and move on. Six months later there are a dozen of these keys, nobody remembers which workflow uses which, and the person who made half of them has left.
That's a problem the day one of those keys leaks. A key with admin rights to your whole account turns a small mistake into a serious one. This guide covers how to give each automation only the access it needs, where to keep the keys, and what to do when one gets out. It leans on the OWASP Secrets Management Cheat Sheet and on what the big platforms say in their own documentation.
Start with what the automation actually does#
Least privilege sounds abstract, but for an automation it's a short exercise. Write down every action the workflow performs and every system it touches. A nightly job that copies new invoices from an accounting tool into a shared drive needs to read invoices and write files to one folder. It doesn't need to delete invoices, change users or read the payroll module.
The OWASP cheat sheet puts it bluntly: "Secrets must have the minimum privileges assigned to them to enable their required use/role." AWS says the same thing in its IAM security best practices, which tell you to "grant only the permissions required to perform a task."
Then match that list against the permission options the service gives you. Some services are generous here. GitHub's fine-grained personal access tokens can be limited to specific repositories, and each token gets specific permissions rather than the broad scopes of the older classic tokens. GitHub calls the classic tokens less secure and recommends fine-grained ones whenever possible. Other services only offer "read-only" or "full access". If that's all you get, pick read-only where you can and treat any full-access key as high risk.
Working out the minimum by hand can be tedious on a big cloud account. AWS offers a shortcut: IAM Access Analyzer can look at the activity CloudTrail has logged for a role and generate a fine-grained policy based on the services and actions it actually used. One way to use it is to run the job for a while in a test account with a looser policy, generate the tighter one, then switch.
One key per automation, and one owner per key#
The easiest way to lose track of access is to share one key across several workflows. When that key leaks you can't tell which job was the weak point, and revoking it breaks everything at once.
Google Cloud's API key best practices recommend separate keys for each team member and each application, which helps with access control and audit trails and limits the damage from a compromised key. The OWASP cheat sheet makes a similar point about CI/CD pipelines: limit shared secrets, and never have one password for all administrative users.
In practice that means a short inventory. For each key, record which automation uses it, who owns it, what it can do, and when it expires. A spreadsheet is fine to start with. Name keys after the job, not the person ("invoice-sync-prod", not "dave-key-2"), so it still makes sense when Dave moves on.
Where a platform offers something better than a personal token, use it. GitHub's docs say personal access tokens are for acting on your own behalf, and that for resources owned by an organisation, or for long-lived integrations, you should use a GitHub App. The same logic applies elsewhere: a service account or app registration belongs to the business, while a personal token disappears or breaks when the person's account is closed.
Prefer short-lived credentials over long-lived keys#
A key that never expires stays useful to an attacker for as long as nobody notices. Short-lived credentials reduce that window.
AWS's IAM best practices say workloads should use temporary credentials through IAM roles. When your code runs on a service such as EC2 or Lambda, AWS delivers the role's temporary credentials to it, so there's no need to hand out long-lived user keys. Google's guidance points the same way, advising you to plan a move from API keys to IAM policies and short-lived service account credentials.
The OWASP cheat sheet goes further with dynamic secrets, generated on demand with a short lease and revoked when no longer needed. One detail from it is worth remembering: restarting an application doesn't revoke credentials that were stolen from it. Revocation has to happen at the provider.
When you're stuck with a long-lived key (plenty of SaaS tools only offer that), set an expiry if the service supports it. GitHub recommends adding an expiration to personal access tokens, and as a safety net it automatically removes tokens that haven't been used in a year. OWASP's advice is to create secrets that expire after a defined time where possible, and to rotate regularly "so that any stolen credentials will only work for a short time." Google recommends periodically creating a replacement key, updating your apps and deleting the old one. AWS frames it as updating long-term access keys when needed, such as when an employee leaves, and using last-used information to remove keys safely.
Where the keys should live#
Hardcoding a key in a script is the classic mistake. Google warns that keys hardcoded in source or stored in a repository "are open to interception or theft by bad actors."
Environment variables are better than source code but not as safe as people assume. The OWASP cheat sheet notes they're generally accessible to all processes and may end up in logs or system dumps, and it doesn't recommend them unless other methods aren't possible. It also says secrets should never be hardcoded with Docker ENV or ARG instructions.
The better home is a secrets manager: your cloud provider's, a dedicated tool, or the encrypted secret store built into your CI platform or workflow tool. What matters is that access to each secret is controlled, logged and revocable. OWASP's minimum for auditing is recording who requested a secret, for what system and role, and keeping those logs tamper-resistant.
Two smaller habits help a lot. Check that your automation's logs and pipeline output don't print secrets (OWASP calls this out for CI/CD). And watch usage: Google notes that monitoring API usage can alert you to unauthorised use, so a key that suddenly makes ten times its usual calls at 3am gets noticed.
Catch leaks before they're pushed#
Committing a key to a Git repository is an easy accident to make. OWASP suggests turning on secret detection at the developer level, in the editor or through a pre-commit hook, so secrets are caught before they're committed.
If you host code on GitHub, push protection blocks pushes that contain recognised secrets before they reach the repository. It covers pushes from the command line, commits made in the web UI, file uploads and REST API requests. User-level push protection is on by default on GitHub.com and stops you pushing secrets to public repositories. Repository-level push protection is off by default and needs an admin to enable it (it requires GitHub Secret Protection). People with write access can bypass a block by giving a reason, and those bypasses show up in the audit log, so it's a guard rail rather than a wall.
When a key leaks, revoke first#
The instinct after committing a key is to delete the commit and rewrite history. That's the wrong first step. GitHub's guide to removing sensitive data says to revoke or rotate the secret first, and that once it's revoked it can no longer be used, which may be all you need.
Rewriting history doesn't reach copies that already exist. The same guide notes you can't remove data from other people's clones, commits stay accessible in any forks, and old commits may still be reachable through cached views and pull requests that reference them. A collaborator's ordinary pull and push can even put the data back.
OWASP's incident steps follow the same order: revoke the exposed key immediately, create and deploy a new one, then remove the old secret from wherever it was exposed, including logs. Use your access logs to work out who had the key and when it was last used. That's where the one-key-per-automation habit pays off, because you know exactly which job to update and nothing else breaks.
A checklist for each new automation#
When we build automations at TheAILAB, these are the questions we ask before a workflow gets its first credential:
- What does this job read and write, and which permissions cover exactly that?
- Can it use a role, service account or app instead of a personal key?
- Does it have its own key, with a named owner and an expiry date?
- Is the key in a secrets store, and are logs checked for leaks?
- If the key leaked tonight, who would revoke it, and what would break?
The last question is the useful one. If the honest answer is "nobody knows" or "everything", fix that before the automation goes live, while it's still cheap.
Sources
- Secrets Management Cheat Sheet — OWASP Cheat Sheet Series (checked 2026-10-14)
- Managing your personal access tokens — GitHub Docs (checked 2026-10-14)
- Security best practices in IAM — Amazon Web Services, AWS Identity and Access Management User Guide (checked 2026-10-14)
- Best practices for managing API keys — Google Cloud Documentation (checked 2026-10-14)
- Push protection — GitHub Docs (checked 2026-10-14)
- Removing sensitive data from a repository — GitHub Docs (checked 2026-10-14)
Dealing with this in your own business?
If you want to use AI without sending sensitive data to outside services, tell us what you're working with and we'll suggest a setup.