VNX-SEC-003 – AWS Secret Access Key
Overview
This rule detects AWS secret access keys embedded in source files by matching variable names such as aws_secret, secret_access_key, or aws_secret_access_key assigned to a 40-character base64 string. The AWS secret access key is the second half of the AWS credential pair; alone it is insufficient for authentication, but when combined with an access key ID (detected by VNX-SEC-001) it provides complete programmatic access to AWS APIs. These two values almost always appear together in the same file or nearby.
Severity: Critical | CWE: CWE-798 – Use of Hard-coded Credentials
Why This Matters
AWS credentials are one of the most targeted secrets in automated scanning operations. Malicious bots continuously crawl public repositories for patterns matching AWS secret keys; a key can be abused for crypto mining, data exfiltration, or infrastructure destruction within minutes of exposure. Secret access keys are 40 characters of base64 and do not have a distinctive prefix like access key IDs, making them harder to detect visually — but the rule matches them by context (the surrounding variable name).
Unlike a password that might be rotated periodically, AWS keys remain valid until explicitly rotated or deleted. Every git clone, CI/CD artifact, or log containing the key becomes a potential leak vector. VNX-SEC-001 and VNX-SEC-003 are designed to be used together since these two values always travel as a pair.
What Gets Flagged
A 40-character base64 value with high entropy, where AWS says it is a secret access key:
- assigned to
aws_secret_access_key,AWS_SECRET_ACCESS_KEY,secret_access_key,secretAccessKey,SecretAccessKey,aws_secret_keyoraws_secretin code, JSON/YAML,.env, shellexportor the~/.aws/credentialsformat; - the
secret_keyargument of a Terraform/HCL file that configures AWS; - the second argument of Go’s
credentials.NewStaticCredentials/NewStaticCredentialsProvider; - a quoted secret passed to an SDK credentials constructor (
new BasicAWSCredentials(id, "...")in Java and .NET,AwsBasicCredentials.create, RubyAws::Credentials.new, PHPCredentials), including when the argument is on one of the next three lines; - the
'secret'entry of a PHP SDK'credentials' => [...]array.
# FLAGGED: shared credentials file
[default]
aws_secret_access_key = <40-character secret>
// FLAGGED: static credentials in Go
creds := credentials.NewStaticCredentials(keyID, "<40-character secret>", "")
Not flagged:
- AWS’s documented example key (ending
EXAMPLEKEY) and placeholders. - 40-character hex digests (a SHA-1 is not a secret key).
- Environment and CI-secret references (
os.environ[...],${{ secrets.AWS_SECRET_ACCESS_KEY }}). - A generic
SECRET_KEY(Django’s, for example) outside an AWS Terraform file. - Test, fixture, vendored and generated files.
The paired access key ID is VNX-SEC-001.
Remediation
Rotate the access key immediately. In the AWS Console go to IAM → Users → your user → Security credentials → Create access key. Delete the old key once the new one is confirmed working.
Verify whether the key was used maliciously. Check AWS CloudTrail for API calls from the compromised key. Look especially for
iam:CreateUser,ec2:RunInstances,s3:GetObject, andsts:AssumeRolecalls that you did not make.Remove from source code. Replace hardcoded values with environment variable lookups:
# SAFE: load from environment
import boto3
import os
session = boto3.Session(
aws_access_key_id=os.environ['AWS_ACCESS_KEY_ID'],
aws_secret_access_key=os.environ['AWS_SECRET_ACCESS_KEY']
)
For services running on AWS (EC2, ECS, Lambda), use IAM instance roles or task roles — no static keys are needed at all. The SDK picks up credentials automatically from instance metadata.
- Use AWS Secrets Manager for non-AWS-hosted workloads:
# SAFE: fetch from AWS Secrets Manager at runtime
import boto3
import json
def get_aws_credentials():
client = boto3.client('secretsmanager', region_name='us-east-1')
secret = client.get_secret_value(SecretId='prod/app/aws-credentials')
return json.loads(secret['SecretString'])
- Scan git history for any traces of the key, then rewrite history with
git-filter-repoif found:
git filter-repo --replace-text <(echo 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY==>REDACTED')
gitleaks detect --source . --verbose
- Set environment variables in CI/CD. Use GitHub Actions secrets, GitLab CI/CD variables, or your pipeline’s secrets management — never hardcode in workflow files either.