Remove AWS credentials file for secure coding agent use
Detailed guide how to use 1Password CLI (op) for AWS credentials so malicious skills cannot extract AWS credentials easily

There is no 100% security, this does not guarantee that attackers cannot steal your AWS credentials. It only reduces the most obvious attack vectors!
If you already know the problem, jump directly to the 1Password CLI solution or the aws-vault solution.
Couple of weeks ago we rolled out Codex for every developer at esome. I have been a heavy user since beginning of the year and it was only natural to do a Q&A session with other heavy users and for those who only got introduced to the style of LLM usage.
What I did not expect: Most of the questions were about security.
- How do you make sure that the LLM does not get access to API keys/ credentials?
- How do you make sure code you did not read does not manipulate something with your AWS credentials?
- How do you make sure you don’t install skills that contain malicious instructions?
During the meeting I mostly answered with “did not care about it, used yolo mode all the time”. But it got me thinking for a solution and I think I found one for my workflow, which at least covers the most obvious attack vectors.
The problem
By default, AWS credentials are stored in plain text in ~/.aws/credentials file. As long as Codex/Claude Code is allowed to execute arbitrary bash commands, a small malicious text in a skill you install could lead to a command executed that reads the content of the file and POST-ing it somewhere with curl. Couple of seconds, credentials gone.
Depending if you were able to spot the command being executed, bad actors could do all kind of things depending on the permissions your AWS user has.
Therefore we want to:
- prevent the credentials stored in plain text all the time
- prevent the credentials stored temporarily in a file over multiple shells
- grant permissions manually every time AWS credentials are accessed
- should work for accessing AWS APIs like bedrock/ textract locally for development
The solution
There are several options for not storing AWS credentials in plain text. All of them revolve somehow about a keychain-like secure backend, depending on your OS. I started with aws-vault which uses macOS keychain, but quickly got annoyed due to buggy/ not working touch ID. And no way I am going to enter my keychain password manually every time my agent wants to access kubectl for debugging an issue. I will still attach relevant scripts for aws-vault below.
Since I use 1Password for my private stuff, I checked it out and they have a first-party plugin for working with AWS CLI.
op (1Password CLI)
- Install CLI & aws-cli extension https://www.1password.dev/cli/shell-plugins/aws
- back up your
.aws/credentialsfile - delete your
.aws/credentialsfile to ensure thatopis the only credential source - Follow setup in https://www.1password.dev/cli/shell-plugins/aws
- test op CLI with AWS:
aws sts get-caller-identity --profile <prod>. You should get prompted with a Touch ID modal for accessing AWS credentials
kubectl
When accessing k8s via AWS, you need to update your ~/.kube/config to request credentials from op secret storage
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: xxx
server: https://xxx.eu-central-1.eks.amazonaws.com
name: arn:aws:eks:eu-central-1:xxx:xxx/xxx
contexts:
- context:
cluster: arn:aws:eks:eu-central-1:xxx:xxx/xxx
user: arn:aws:eks:eu-central-1:xxx:xxx/xxx
name: xxx
current-context: xxx
kind: Config
users:
- name: arn:aws:eks:eu-central-1:xxx:xxx/xxx
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
args:
# this is the relevant section you have to update in your config
- -lc
- exec op plugin run -- aws --region eu-central-1 eks get-token --cluster-name eks01-prd --output json
command: zsh
env:
- name: AWS_PROFILE
value: prd
interactiveMode: IfAvailable
provideClusterInfo: false
Application credentials (Node.js)
If your locally running service you are developing needs access to AWS resources, e.g. AWS Bedrock, this whole thing might break apart. Your application does not know about a shell or the created alias from 1Password.
In these cases you can use 1Password environments.

- Create an environment in 1Password with these 3 env vars
- Create methods for authenticating in production (via IAM roles) and locally (via environment variables)
- Keep in mind: if you are assuming IAM roles locally, you need wrap it into
fromTemporaryCredentialsprovider, using the correct role - Copy the environment key from the 1Password interface at three dots -> Copy environment ID
- You can start applications with
op run --environment xxx -- bun run dev
import { fromNodeProviderChain, fromTemporaryCredentials } from '@aws-sdk/credential-providers';
import type { AwsCredentialIdentity, Provider } from '@aws-sdk/types';
interface AwsCredentialOptions {
accessKeyId?: string;
secretAccessKey?: string;
sessionToken?: string;
profile?: string;
region?: string;
roleArn?: string;
roleSessionName?: string;
}
// Local static env credentials need to assume the DevOps role before calling Bedrock.
const buildAssumeRoleCredentialProvider = ({
masterCredentials,
region,
roleArn,
roleSessionName,
}: {
masterCredentials: AwsCredentialIdentity | Provider<AwsCredentialIdentity>;
region?: string;
roleArn: string;
roleSessionName?: string;
}): Provider<AwsCredentialIdentity> =>
fromTemporaryCredentials({
masterCredentials,
params: {
RoleArn: roleArn,
RoleSessionName: roleSessionName ?? 'xxx',
},
clientConfig: region ? { region } : undefined,
});
const buildStaticCredentials = ({
accessKeyId,
secretAccessKey,
sessionToken,
}: Required<Pick<AwsCredentialOptions, 'accessKeyId' | 'secretAccessKey'>> &
Pick<AwsCredentialOptions, 'sessionToken'>): AwsCredentialIdentity => ({
accessKeyId,
secretAccessKey,
...(sessionToken ? { sessionToken } : {}),
});
export const buildAwsCredentialProvider = ({
accessKeyId,
secretAccessKey,
sessionToken,
profile,
region,
roleArn,
roleSessionName,
}: AwsCredentialOptions = {}): Provider<AwsCredentialIdentity> | undefined => {
if (accessKeyId && secretAccessKey) {
const credentials = buildStaticCredentials({
accessKeyId,
secretAccessKey,
sessionToken,
});
if (roleArn) {
return buildAssumeRoleCredentialProvider({
masterCredentials: credentials,
region,
roleArn,
roleSessionName,
});
}
return async () => credentials;
}
if (profile) {
return fromNodeProviderChain({ profile });
}
return fromNodeProviderChain();
};
export const buildAwsClientCredentials = buildAwsCredentialProvider;
aws-vault
- Follow Setup guide here
- back up your
.aws/credentialsfile - delete your
.aws/credentialsfile to ensure thataws-vaultis the only credential source - test aws-vault credentials:
aws-vault exec <prod> -- aws sts get-caller-identity
kubectl
When accessing k8s via AWS, you need to update your ~/.kube/config to request credentials from aws-vault
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: xxx
server: https://xxx.eu-central-1.eks.amazonaws.com
name: arn:aws:eks:eu-central-1:xxx:xxx/xxx
contexts:
- context:
cluster: arn:aws:eks:eu-central-1:xxx:xxx/xxx
user: arn:aws:eks:eu-central-1:xxx:xxx/xxx
name: xxx
current-context: xxx
kind: Config
users:
- name: arn:aws:eks:eu-central-1:xxx:xxx/xxx
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
# this is the relevant section you have to update in your config
args:
- exec
- prd
- --
- aws
- --region
- eu-central-1
- eks
- get-token
- --cluster-name
- xxx
- --output
- json
command: aws-vault
env:
- name: AWS_PROFILE
value: prd
interactiveMode: IfAvailable
provideClusterInfo: false
Application Credentials (Node.js)
There appears to be a way to integrate Docker with aws-vault, but the setup looks rather complex, and I haven’t tried it yet:
https://github.com/ByteNess/aws-vault/blob/main/USAGE.md#docker