# AWS Secrets Manager Provider

> AWS Secrets Manager integration

The [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) provider integrates with AWS for centralized secret management.

## At a glance

|                 |                                                 |
| --------------- | ----------------------------------------------- |
| Provider        | `awssm`                                         |
| URI             | `awssm://[AWS_PROFILE@]REGION[?options]`        |
| Access          | Read and write; secret references are read-only |
| Best for        | Workloads and teams on AWS                      |
| Authentication  | Standard AWS SDK credential chain               |
| Build feature   | `awssm`                                         |
| Default storage | `[prefix/]secretspec/{project}/{profile}/{key}` |

## Quick start

```bash
# Set a secret
$ secretspec set DATABASE_URL --provider awssm://us-east-1
Enter value for DATABASE_URL: postgresql://localhost/mydb
✓ Secret 'DATABASE_URL' saved to awssm (profile: default)


# Run with secrets
$ secretspec run --provider awssm://us-east-1 -- npm start
```

## Setup

### Prerequisites

* AWS account with Secrets Manager access
* AWS credentials configured (CLI, environment variables, IAM roles, or SSO)
* Build with `--features awssm`

### Authentication

AWS Secrets Manager uses the standard AWS SDK credential chain:

1. Environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`)
2. Shared credentials file (`~/.aws/credentials`)
3. AWS SSO (`aws sso login`)
4. IAM roles (EC2 instance profiles, ECS task roles, Lambda execution roles)

### Required IAM permissions

For identities used only to read secrets, such as those running `secretspec get`, `secretspec check`, or `secretspec run`, use a read-only policy. Replace the example region and account ID with your own:

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "SecretspecBatchFetch",
      "Effect": "Allow",
      "Action": "secretsmanager:BatchGetSecretValue",
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "aws:RequestedRegion": "us-east-1"
        }
      }
    },
    {
      "Sid": "SecretspecRead",
      "Effect": "Allow",
      "Action": "secretsmanager:GetSecretValue",
      "Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:secretspec/*"
    }
  ]
}
```

Identities that run `secretspec set` also need this statement in the policy’s `Statement` array:

```json
{
  "Sid": "SecretspecWrite",
  "Effect": "Allow",
  "Action": [
    "secretsmanager:CreateSecret",
    "secretsmanager:PutSecretValue"
  ],
  "Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:secretspec/*"
}
```

If you use a prefix such as `?prefix=myteam`, adjust the secret ARN in the read and write statements:

```plaintext
arn:aws:secretsmanager:us-east-1:123456789012:secret:myteam/secretspec/*
```

> **Note**
>
> `BatchGetSecretValue` is used automatically during `check` and `run` to fetch secrets in batches of 20 instead of one call each.
>
> AWS Secrets Manager [does not support resource-level permissions](https://docs.aws.amazon.com/service-authorization/latest/reference/list_secretsmanager.html) for `BatchGetSecretValue`, so that action must use `"Resource": "*"`. Scoping it to a secret ARN does not grant the permission, and the batch request fails with `AccessDeniedException`. The `aws:RequestedRegion` condition limits the wildcard statement to the configured region.
>
> The wildcard does not authorize access to secret contents by itself. AWS also requires `secretsmanager:GetSecretValue` for every secret returned by a batch, and that permission remains scoped to the secret ARN. SecretSpec supplies an explicit list of secret IDs, so the [filter-only `ListSecrets` permission](https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_BatchGetSecretValue.html) is not required.

> **Note**
>
> Using `tag.NAME=VALUE` additionally requires `secretsmanager:TagResource`, and a `kms_key_id` requires `kms:GenerateDataKey` and `kms:Decrypt` on that key.

## Configuration

### URI format

```plaintext
awssm://[AWS_PROFILE@]REGION[?prefix=PREFIX][&kms_key_id=KEY][&tag.NAME=VALUE...]
```

* `REGION`: AWS region (e.g., `us-east-1`). If omitted, the SDK default region chain is used.
* `AWS_PROFILE`: Optional AWS profile from `~/.aws/credentials`. If omitted, the SDK default credential chain is used.
* `PREFIX`: Optional root prefix prepended to all secret names. Useful when IAM policies scope access by prefix (e.g., only allow `myteam/*`).
* `kms_key_id`: Optional KMS key (id, ARN, or `alias/...`) used to encrypt secrets that secretspec creates.
* `tag.NAME=VALUE`: Optional tags applied to secrets that secretspec creates. Repeat for multiple tags.

`kms_key_id` and `tag.NAME=VALUE` are applied **only when secretspec creates a secret** (`CreateSecret`); updating a value (`PutSecretValue`) accepts neither, and a pre-existing secret keeps the key and tags it was created with. This supports AWS “tag-on-create” guardrails, where an SCP or IAM condition denies `CreateSecret` unless required `aws:RequestTag/*` tags (and often a customer-managed key) are present in the same call.

### URI examples

```text
awssm://us-east-1
awssm://production@us-east-1
awssm://us-east-1?prefix=myteam
awssm://prod@us-east-1?kms_key_id=alias/my-key&tag.team=platform&tag.env=prod
awssm
```

### Project configuration

Because guardrail tags and keys usually vary per environment, they are a natural fit for a checked-in [provider alias](/reference/configuration/) in `secretspec.toml`:

```toml
[providers]
prod = "awssm://prod@us-east-1?kms_key_id=alias/my-key&tag.team=platform&tag.env=prod"
```

Route secrets through the alias in project configuration:

**secretspec.toml**

```toml
[profiles.production]
DATABASE_URL = { description = "Database URL", providers = ["prod"] }
```

## Storage model

**Changed in version 0.21**

AWS `SecretBinary` values are preserved as bytes, and non-UTF-8 writes use `SecretBinary` automatically.

Secrets are stored as `[prefix/]secretspec/{project}/{profile}/{key}`.

For example, `DATABASE_URL` in project `myapp` and profile `production` is stored as `secretspec/myapp/production/DATABASE_URL`. With `?prefix=myteam`, it becomes `myteam/secretspec/myapp/production/DATABASE_URL`.

In SecretSpec 0.21+, reads accept both AWS `SecretString` and `SecretBinary`. Writes use `SecretString` for UTF-8 values and `SecretBinary` for other bytes. In 0.21+, `get` and the Rust byte resolution APIs return binary values inline, and `run` preserves non-UTF-8 bytes on Unix (except NULs). Declare a binary secret with `as_path = true` when an application needs a file. Text SDK responses and text exports require UTF-8.

## Use existing secrets

A secret’s [`ref`](/reference/configuration/#secret-references) field names an existing secret instead: `item` is the secret name (or ARN), and the optional `field` selects one key of a JSON secret value and therefore requires UTF-8. Without `field`, the whole secret is returned, including `SecretBinary` bytes in SecretSpec 0.21+. References are **read-only** in this provider.

```toml
[profiles.production]
# Whole secret value
DATABASE_URL = { description = "DB", ref = { item = "prod/database-url" }, providers = ["awssm://us-east-1"] }
# One key of a JSON secret value
DB_PASSWORD = { description = "DB pw", ref = { item = "prod/db-credentials", field = "password" }, providers = ["awssm://us-east-1"] }
```

## CI/CD

```bash
# Using environment variables
$ export AWS_ACCESS_KEY_ID=AKIA...


$ export AWS_SECRET_ACCESS_KEY=...


$ export AWS_DEFAULT_REGION=us-east-1


# Run command
$ secretspec run --provider awssm://us-east-1 -- deploy


# Or with IAM roles (no credentials needed)
$ secretspec run --provider awssm://us-east-1 -- deploy
```

## Secret revisions

**New in version 0.21**

IPC resolution returns an optional opaque `revision` derived from the full AWS secret ARN, the version ID returned with its value, and the selected JSON field. Rotation changes the revision; a recreated secret has a different ARN. A new version may change the revision even when the selected value is unchanged. SecretSpec also accounts for its own extraction and decoding before returning the logical value’s revision.

The token uses non-secret version metadata, never the secret value. AWS writers must use `ClientRequestToken` as non-secret metadata, as intended by AWS; a secret-derived version ID cannot satisfy this contract. Missing ARN or version metadata produces an unknown revision.

Both single and batch reads preserve revisions. A SecretSpec cache returns the revision of its cached value, so rotation can remain unobserved until refresh. See [resolver revision semantics](/reference/resolver-protocol#secret-revisions) for task-cache integration and unknown-revision handling.