Skip to main content
The golden rule when deploying Lowkey: never put raw API keys or tokens on the installer command line. They end up in shell history, /proc/<pid>/cmdline, CloudFormation state, and CI logs. Lowkey’s --kiro-from-secret flag shows the right pattern — pass only a Secrets Manager reference at deploy time, and let the instance resolve the raw value via its IAM role at install time.
Only the secret name flows through CloudFormation or Terraform state. The value is resolved on the EC2 instance at install time, using the instance’s IAM role. It never appears in deploy state, UserData logs, or shell history.

How the reference pattern works

  1. You store the raw secret in AWS Secrets Manager under a path like /lowkey/kiro-api-key.
  2. You pass the path (not the value) to the installer with --kiro-from-secret /lowkey/kiro-api-key.
  3. The installer passes this reference through CloudFormation or Terraform state — only the name, not the value.
  4. On the EC2 instance, the pack’s install.sh calls aws secretsmanager get-secret-value using the instance’s IAM role.
  5. The resolved key is written to a protected file (for example, ~/.kiro/env with 0600 permissions) and sourced at login.
The raw key is never in CloudFormation state, Terraform state, UserData metadata, or shell history anywhere along this path.

Step-by-step: Kiro CLI headless deploy

1

Store the Kiro API key in Secrets Manager

aws secretsmanager create-secret \
  --name /lowkey/kiro-api-key \
  --secret-string "$KIRO_API_KEY"
The SecretString is just the raw API key — no JSON wrapper. You can also create this in the AWS console.
2

Verify the instance profile has permission

Every Lowkey profile (builder, account_assistant, personal_assistant) includes secretsmanager:GetSecretValue on secrets under the /lowkey/* path by default. If you stored the key under /lowkey/kiro-api-key, no additional IAM changes are needed.
3

Run the installer with the secret reference

curl -sfL install.lowkey.run | bash -s -- -y \
  --pack kiro-cli \
  --profile builder \
  --kiro-from-secret /lowkey/kiro-api-key
4

The pack resolves and stores the key on the instance

At install time, the pack runs:
aws secretsmanager get-secret-value \
  --secret-id /lowkey/kiro-api-key \
  --query SecretString \
  --output text
The resolved key is written to ~/.kiro/env with 0600 permissions and sourced from ~/.bash_profile.
5

Rotate by updating the secret and re-running

Update the secret value:
aws secretsmanager put-secret-value \
  --secret-id /lowkey/kiro-api-key \
  --secret-string "$NEW_KIRO_API_KEY"
Then redeploy the stack to pick up the new value, or update the key file on the instance manually:
aws ssm start-session --target <instance-id>

# Fetch the new value and update the env file
aws secretsmanager get-secret-value \
  --secret-id /lowkey/kiro-api-key \
  --query SecretString \
  --output text > ~/.kiro/env
chmod 600 ~/.kiro/env

Other secret types

The codex-cli pack does not yet have a --from-secret flag. To avoid putting the key in your shell history, store it in Secrets Manager and source it manually after connecting via SSM:
aws ssm start-session --target <instance-id>

export OPENAI_API_KEY=$(aws secretsmanager get-secret-value \
  --secret-id /lowkey/openai-api-key \
  --query SecretString \
  --output text)

echo "$OPENAI_API_KEY" | codex login --with-api-key
For persistence across sessions, write to ~/.bash_profile with 0600 permissions.
When you deploy OpenClaw with model-mode=api-key, the agent uses ANTHROPIC_API_KEY directly instead of Bedrock. Store the key in Secrets Manager and fetch it on the instance:
export ANTHROPIC_API_KEY=$(aws secretsmanager get-secret-value \
  --secret-id /lowkey/anthropic-api-key \
  --query SecretString \
  --output text)
Write to ~/.bash_profile (0600) for persistence. Alternatively, pass --provider-api-key at install time — but note that this value passes through UserData. See the warning below.
Pass via --litellm-api-key to the installer. The CloudFormation template marks it NoEcho: true, so it won’t appear in describe-stacks output. However, it still appears in Base64-encoded UserData in stack metadata.For production, fetch the key from Secrets Manager inside a post-install script instead. A --litellm-from-secret flag is planned — check the GitHub repo for current status.
NemoClaw accepts --telegram-token during install. Like other API key flags, this is NoEcho: true in CloudFormation but still present in UserData. For a more secure setup, store the token in Secrets Manager and load it in a post-install script rather than passing it on the command line.

Why not use CloudFormation NoEcho directly?

NoEcho: true hides parameter values from describe-stacks output and the CloudFormation console. It is not a complete solution:
  • Values still appear in the CloudFormation template body if set as a Default:.
  • They appear in Base64-encoded UserData, which is queryable via describe-instance-attribute.
  • Terraform stores sensitive = true variables in plaintext in its state file.
  • Both leave a trail in CI logs if the deploy command is logged.
The Secrets Manager reference pattern avoids all of this. Only the secret name is in deploy state; the value is resolved at install time via IAM.

Secret naming convention

Store Lowkey-related secrets under the /lowkey/ prefix. The default IAM policy on every instance profile grants secretsmanager:GetSecretValue on this path:
{
  "Effect": "Allow",
  "Action": "secretsmanager:GetSecretValue",
  "Resource": "arn:aws:secretsmanager:*:*:secret:/lowkey/*"
}
If you store secrets under a different prefix, update the IAM policy in advanced mode or by editing the CloudFormation template directly before deploying.