> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lowkey.run/llms.txt
> Use this file to discover all available pages before exploring further.

# Store and use API secrets with AWS Secrets Manager

> How Lowkey uses Secrets Manager to keep API keys out of deploy state — step-by-step Kiro headless setup and patterns for Codex, Anthropic, and LiteLLM keys.

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.

<Note>
  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.
</Note>

## 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

<Steps>
  <Step title="Store the Kiro API key in Secrets Manager">
    ```bash theme={null}
    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.
  </Step>

  <Step title="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.
  </Step>

  <Step title="Run the installer with the secret reference">
    ```bash theme={null}
    curl -sfL install.lowkey.run | bash -s -- -y \
      --pack kiro-cli \
      --profile builder \
      --kiro-from-secret /lowkey/kiro-api-key
    ```
  </Step>

  <Step title="The pack resolves and stores the key on the instance">
    At install time, the pack runs:

    ```bash theme={null}
    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`.
  </Step>

  <Step title="Rotate by updating the secret and re-running">
    Update the secret value:

    ```bash theme={null}
    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:

    ```bash theme={null}
    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
    ```
  </Step>
</Steps>

## Other secret types

<AccordionGroup>
  <Accordion title="OpenAI API key (Codex CLI pack)">
    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:

    ```bash theme={null}
    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.
  </Accordion>

  <Accordion title="Anthropic API key (OpenClaw model-mode=api-key)">
    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:

    ```bash theme={null}
    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.
  </Accordion>

  <Accordion title="LiteLLM proxy API key">
    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.
  </Accordion>

  <Accordion title="Telegram bot token (NemoClaw pack)">
    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.
  </Accordion>
</AccordionGroup>

## 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:

```json theme={null}
{
  "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.
