Your GitHub Actions Workflow Has No AWS Keys. Is It Actually Secure?
Why temporary credentials alone don't guarantee a secure AWS deployment pipeline.
Your GitHub Actions workflow no longer stores AWS access keys. Instead, it uses OpenID Connect to request temporary credentials. That's a significant improvement. But does it actually mean your deployment pipeline is secure?
The answer depends on something that receives considerably less attention than removing secrets: the trust relationship between GitHub and AWS.
OIDC changes how a workflow authenticates, but it does not automatically establish which workflows should be authorised to access production infrastructure.
While working on my ECS deployment platform, I encountered problems involving GitHub's OIDC identity claims and AWS IAM trust policies. That experience raised a more interesting question: what exactly is AWS trusting when it accepts a token issued to a GitHub Actions job?
The problem OIDC was designed to solve
Traditionally, CI/CD pipelines authenticated with AWS using an access key ID and secret access key belonging to an IAM user.
Those credentials could be stored in GitHub repository secrets and injected into a workflow whenever a deployment needed access to AWS.
Although GitHub encrypts repository secrets, long-lived credentials still create operational risks.
If credentials are exposed through a compromised workflow, an accidental log entry or another security failure, they may remain usable until explicitly revoked or rotated.
OIDC removes the need for this arrangement.
Instead of retrieving an existing AWS secret, a GitHub Actions job requests a short-lived identity token from GitHub's OIDC provider.
AWS Security Token Service evaluates the token against an IAM role's trust policy and, if the conditions are satisfied, issues temporary AWS credentials.
GitHub Actions job
|
v
Requests an OIDC token
|
v
GitHub OIDC identity provider
|
v
Signed identity token
|
v
AWS Security Token Service
|
v
IAM trust policy evaluation
|
+-- Denied
|
+-- Allowed
|
v
Temporary AWS credentialsThe improvement is substantial. There is no persistent AWS access key to distribute to the workflow, and the credentials issued by AWS have a limited lifetime.
However, temporary credentials can still be misused during their validity period. Their short lifespan is one security control, not a complete authorisation model.
What AWS actually evaluates
When GitHub issues an OIDC token, it includes claims describing the identity and execution context of the requesting job.
These claims allow the receiving system to make decisions about whether that identity should be trusted.
For AWS IAM integrations, two particularly important claims are the audience and subject.
The audience identifies the intended recipient of the token. The subject describes the GitHub workflow context requesting access.
A conventional subject for a workflow running on the main branch might look like this:
repo:example-org/example-repo:ref:refs/heads/mainA job associated with a GitHub environment may instead present a subject resembling:
repo:example-org/example-repo:environment:productionThese examples reflect conventional GitHub subject formats. GitHub also supports customised subject templates, so the actual token must be inspected rather than assumed.
The IAM role's trust policy determines whether AWS accepts the identity represented by those claims.
If the trust conditions are too broad, more workflow contexts may be authorised than intended. If they are too restrictive or do not match the actual token, legitimate deployments can fail.
The repository-wide trust problem
Consider a repository containing several GitHub Actions workflows. One runs unit tests, another builds container images, and a third deploys the application to production.
Only the production deployment workflow should be able to assume a role with production deployment permissions.
But imagine the IAM role uses a wildcard that trusts every OIDC subject associated with the repository.
{
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud":
"sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub":
"repo:example-org/example-repo:*"
}
}
}This is an illustrative condition block, not a complete IAM role trust policy.
The wildcard allows multiple workflow contexts within the repository to match the subject condition.
That is broader than a policy intended to authorise only one production deployment path.
It does not mean every workflow automatically receives credentials. It means the trust condition itself may not distinguish between the contexts that matter.
The problem is not that the token is fake or that AWS STS is insecure.
The problem is that the organisation has described an overly broad set of identities as trusted.
Authentication and authorisation are different
An OIDC token establishes information about the identity and context of a requesting workflow.
It does not automatically establish that the workflow should be permitted to deploy production infrastructure.
AWS IAM separates these concerns.
The role's trust policy determines which principals can assume the role. Its permissions policies determine what those principals can do after assuming it.
Both decisions matter.
Imagine an IAM role that can update a production ECS service.
Even if its permissions are carefully restricted to that deployment operation, it is dangerous if unintended workflows can assume it.
Equally, a narrowly restricted trust policy does not make a role safe if the resulting session receives unnecessarily broad administrative permissions.
The identity allowed to assume a role and the actions permitted after assumption are separate parts of the same security boundary.
Restricting the deployment context
If the intended deployment identity is a conventional main-branch context, a stricter trust condition can require that exact subject.
{
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud":
"sts.amazonaws.com",
"token.actions.githubusercontent.com:sub":
"repo:example-org/example-repo:ref:refs/heads/main"
}
}
}This narrows the set of workflow contexts that can assume the role.
However, a branch restriction is not necessarily a workflow restriction.
Multiple workflows may run on the same branch. If an attacker can modify a workflow on that branch or otherwise cause an authorised context to execute malicious steps, the branch condition alone is not a complete defence.
Repository write permissions, branch protection, code review and control over workflow changes therefore become part of the AWS security model, even though those controls exist on GitHub's side of the integration.
The security of a deployment role depends partly on the security of the repository and workflow configuration permitted to assume it.
Why protected environments help
GitHub environments provide another mechanism for controlling deployment workflows.
A production environment can be configured with deployment restrictions and approval requirements, depending on the repository's available GitHub features.
When a job uses an environment, its conventional OIDC subject can reflect that environment.
AWS can then restrict role assumption to the expected environment subject.
{
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud":
"sts.amazonaws.com",
"token.actions.githubusercontent.com:sub":
"repo:example-org/example-repo:environment:production"
}
}
}That condition should be paired with appropriate GitHub environment protection settings.
Merely naming an environment production does not make it protected.
The value comes from combining GitHub's controls over who can reach that environment with AWS's restriction on which environment identity it accepts.
Neither side should be evaluated in isolation.
What about individual workflow identity?
For some organisations, even an environment-based subject may be too broad.
A repository may contain several workflows capable of targeting the same environment, while only a centrally reviewed deployment workflow should receive production credentials.
GitHub supports customising OIDC subject claims, including designs involving reusable workflows and workflow identity.
These controls can help distinguish an approved deployment workflow from other jobs sharing a repository, branch or environment.
The precise claim template and IAM condition must be validated against the actual GitHub and AWS integration rather than assumed.
The design principle is straightforward: if the security requirement names a particular workflow, the trust boundary should ideally be able to identify that workflow rather than merely the repository containing it.
The GitHub permission people misunderstand
A GitHub Actions job needs permission to request an OIDC identity token.
permissions:
contents: read
id-token: writeThe id-token: write permission allows the job to request an identity token from GitHub.
It does not itself grant AWS access.
AWS must still decide whether the token satisfies the IAM role's trust policy.
This distinction becomes useful when troubleshooting authentication failures.
If the workflow cannot request a token, the issue may involve GitHub Actions permissions.
If the workflow can request a token but cannot assume the AWS role, the issue may involve token claims or IAM trust conditions.
If role assumption succeeds but an ECS or ECR operation fails, that is a different authorisation problem involving the role's permissions or another AWS control.
Treating these as separate failure points makes debugging more systematic.
Least privilege is the second half of the boundary
A narrowly written trust policy can still introduce excessive risk if the assumed role has administrator-level permissions.
A workflow responsible for pushing images to ECR does not automatically need permission to modify networking, IAM or databases.
Likewise, an application release workflow and a Terraform infrastructure workflow may deserve separate IAM roles.
Separating responsibilities makes mistakes easier to contain.
If a build workflow is compromised, its credentials should not provide a general-purpose route to production infrastructure.
Temporary credentials limit how long a credential remains usable. Least privilege limits what that credential can do.
Both controls are necessary because they address different dimensions of risk.
How I would validate the trust boundary
A trust policy is more convincing when both the expected successful and expected denied cases have been tested.
I would start with an isolated test repository and an AWS IAM role with minimal permissions.
The objective would be to compare role assumption from different branches, environments and workflow contexts without giving the test role access to production infrastructure.
The first test would use a broad repository subject. Subsequent tests would narrow the subject to a particular branch or protected environment.
For each run, I would record the workflow context, expected result and whether STS accepted the token.
A successful session could be identified using:
aws sts get-caller-identityThis command reports the AWS identity associated with the active credentials.
It does not prove that the identity has permission to deploy infrastructure. That separation is useful because the trust boundary can be investigated without granting the test workflow unnecessary privileges.
This describes a proposed validation method rather than completed experimental results.
The important idea is to verify not only that an authorised workflow can assume the role, but that unauthorised workflow contexts are denied.
The security improvement that actually matters
OIDC removes an important class of credential-management risk.
It does not remove the need to think about who can edit workflows, which execution contexts may request tokens, how AWS evaluates identity claims and what permissions the resulting session receives.
A deployment pipeline is only as secure as the combined trust boundary between its source-control system, identity provider and cloud environment.
The question I would ask during a pipeline review is not simply whether access keys have been removed.
I would ask whether a workflow that should not deploy can nevertheless satisfy the role's trust conditions, and whether anyone has tested that it cannot.