@aws-blocks/bb-secret

中文文档
0.3.0发布于 2026/11/02Apache-2.0TypeScriptES Module构建来源已验证近 30 天下载 4.1万

A single application secret backed by AWS Secrets Manager.

原始使用文档
文档目录18 节
  1. API
  2. Options
  3. Read-only by default ( allowRuntimeWrite )
  4. Error handling
  5. Examples
  6. Auto-generated signing key (the app creates the value)
  7. Manually-configured credential (you set an externally-issue…
  8. Typed JSON secret with schema validation
  9. Wrap an existing secret
  10. Managing secret values
  11. 1. Auto-generated (random) — no value to set
  12. 2. Set via the settings endpoint → console — the default fo…
  13. 3. Well-known name + the aws CLI — for CI/CD-driven workflo…
  14. Rotating & removing
  15. Which approach, when
  16. Reading an earlier version (rotation grace window)
  17. Scaling & cost (AWS)
  18. Local development

Secret

A single application secret backed by AWS Secrets Manager.

When to use: You need to store a high-value credential — a third-party API key, OAuth client secret, database connection string, webhook signing key, or encryption key. Each Secret instance maps to exactly one Secrets Manager secret.

When NOT to use: For non-sensitive configuration (feature flags, API URLs, thresholds) use AppSetting — it is free for standard values. For application state, use KVStore or DistributedTable.

Secret is the recommended home for production secrets. For any credential that protects a production app — a database connection string, API key, OAuth token, or private key — use Secret. It is purpose-built for secrets: a dedicated per-secret resource with its own access controls, cross-account sharing, version history, and (future) managed rotation. This mirrors AWS's own guidance on where credentials belong.

AppSetting with secret: true also encrypts the value at rest and is free, so it is a reasonable choice for a low-value secret in a prototype, or when you want to avoid the per-secret cost. But for anything protecting a production app, reach for Secret.

Design & mock/AWS parity details: DESIGN.md

API

const secret = new Secret(scope, id, options?)
MethodReturnsDescription
get(options?)Promise<T | null>Read the value. null when unset/missing. options.version selects 'current' (default), 'previous', or a SecretVersion from listVersions().
put(value)Promise<void>Set or update the value at runtime. Only present when the secret was created with allowRuntimeWrite: true — otherwise it is absent from the object (and the type). The prior value becomes the 'previous' version.
listVersions()Promise<SecretVersionInfo[]>List retained versions (metadata only, newest first — never values).
Secret.fromExisting(arn)ExternalSecretRefReference an existing Secrets Manager secret (static). Pass to options.secret.

Options

OptionTypeRequiredDescription
namestringNoExplicit Secrets Manager secret name. When omitted, derived from the scope tree (unique per stack). Provide a well-known name when a team/CI pipeline sets or rotates the value out-of-band via the aws CLI. You own uniqueness across the account/region when set. Ignored for fromExisting.
allowRuntimeWritebooleanNoDefaults to false. When false/omitted the secret is read-only at runtime: no put() method on the object, and the execution role gets read-only IAM (no PutSecretValue). Set true only when the app sets/rotates the value itself at runtime — then put() is present and write IAM is granted. Applies to every secret, including fromExisting.
schemaStandardSchemaV1<T>NoRuntime validation schema (Zod, Valibot, ArkType). Infers T, treats the stored value as JSON, and validates on get()/put(). Omit for an opaque string.
secretExternalSecretRefNoWrap an existing Secrets Manager secret from Secret.fromExisting(arn) instead of creating one.
generatebooleanNoWhen true, seed a random value on first create (for a signing/HMAC key that only needs to exist). Otherwise the secret starts empty and get() returns null until set. Don't combine with schema.
cacheTtlSecondsnumberNoHow long the runtime reuses a cached value before re-fetching. Defaults to 300s (5 min), matching the AWS Secrets Manager agent — a bounded default so a long-running process can't serve a rotated value indefinitely. Raise it to cut fetches, lower it for fresher reads, or set 0 to disable caching. put() always clears the cache. Ignored by the mock.
removalPolicy'destroy' | 'retain'NoOverride the stack-wide removal default for the created secret. Ignored by the mock and when wrapping an existing secret.
loggerChildLoggerNoOptional logger for internal operations. Defaults to an error-level logger.

Read-only by default (allowRuntimeWrite)

A Secret is read-only at runtime unless you opt in. By default the instantiated object exposes only get() and listVersions() — there is no put() (a call is a compile error), and the deployed execution role is granted read-only IAM. This is least-privilege: most apps read a credential that is set out-of-band (console, aws CLI, or CI), so the running app never needs write access.

// Read-only (default): no put(). Set the value out-of-band, read it with get().
const stripeKey = new Secret(scope, 'stripe-api-key');
const key = await stripeKey.get();
// stripeKey.put('…')  ❌ compile error — put is absent

// Writable: the app sets/rotates the value itself. put() is present; the role
// is granted PutSecretValue.
const token = new Secret(scope, 'self-rotating-token', { allowRuntimeWrite: true });
await token.put('t-abc123');

This also applies to a secret wrapped via fromExisting — it is read-only unless you pass allowRuntimeWrite: true.

Error handling

import { isBlocksError } from '@aws-blocks/core';
import { Secret, SecretErrors } from '@aws-blocks/bb-secret';

const secret = new Secret(scope, 'api-key', { allowRuntimeWrite: true });

try {
  await secret.put(value);
} catch (e: unknown) {
  if (isBlocksError(e, SecretErrors.ValidationFailed)) {
    // schema validation failed
  }
  if (isBlocksError(e, SecretErrors.SecretNotFound)) {
    // the underlying Secrets Manager secret does not exist
  }
  throw e;
}

get() returns null for a missing value rather than throwing, so a missing secret is normal control flow, not an exception:

const key = await stripeKey.get();
if (key === null) throw new Error('Stripe key not configured');

Examples

Auto-generated signing key (the app creates the value)

Pass generate: true and never set a value — Secrets Manager creates a random one on first deploy (the mock seeds an equivalent value locally). Good for a JWT/session signing key or HMAC key, where the app generates the secret and nobody needs to read it.

const signingKey = new Secret(scope, 'session-signing-key', { generate: true });

export const api = new ApiNamespace(scope, 'api', (context) => ({
  async issueToken(userId: string) {
    const key = await signingKey.get(); // random value, created on first deploy
    // sign(userId, key)...
  },
}));

Without generate: true, a BB-created secret starts empty: get() returns null until a value is set. Don't combine generate with schema — a random string isn't valid JSON.

Manually-configured credential (you set an externally-issued value)

A third-party key (Stripe, an OAuth client secret) is not auto-generated — you set it out-of-band after deploy. The declaration is the same; the difference is that someone provides the value.

const stripeKey = new Secret(scope, 'stripe-api-key');

export const api = new ApiNamespace(scope, 'api', (context) => ({
  async charge(amount: number) {
    const key = await stripeKey.get();
    if (key === null) throw new Error('Stripe key not configured');
    // use key...
  },
}));

Set the value once — via the settings console route, a deploy script, or the AWS CLI (see "Managing secret values"). If the app itself updates the value at runtime, create the secret with allowRuntimeWrite: true and call put():

const stripeKey = new Secret(scope, 'stripe-api-key', { allowRuntimeWrite: true });
await stripeKey.put('sk_live_...');

Typed JSON secret with schema validation

import { z } from 'zod';

const dbConfig = new Secret(scope, 'db-config', {
  schema: z.object({ host: z.string(), port: z.number() }),
});

export const api = new ApiNamespace(scope, 'api', (context) => ({
  async connect() {
    const config = await dbConfig.get(); // { host: string; port: number } | null
    if (config === null) throw new Error('DB config not set');
    // connect(config.host, config.port)...
  },
}));

Wrap an existing secret

const legacy = new Secret(scope, 'legacy-key', {
  secret: Secret.fromExisting('arn:aws:secretsmanager:us-east-1:123456789012:secret:my-secret-AbCdEf'),
});
const value = await legacy.get();

AWS Blocks will not create, seed, or delete the referenced secret — it only reads its value (and writes it when allowRuntimeWrite: true) and grants the app access.

Managing secret values

A Secret declaration carries only the name — never the value. How you set the value depends on how centrally the secret is managed. There are three complementary approaches:

1. Auto-generated (random) — no value to set

Pass generate: true and never call put(): the secret is created with a random value on first deploy (the mock seeds an equivalent value locally). Use this for opaque values nobody needs to know — session signing keys, HMAC keys. Read it with get(); it is stable across redeploys. Without generate: true the secret starts empty and get() returns null until you set a value.

2. Set via the settings endpoint → console — the default for most apps

Every AWS Blocks app exposes a built-in /aws-blocks/settings route. Deployed, it redirects to the stack's settings resource group in the AWS console, where your Secret values appear alongside your AppSetting parameters — set or rotate them in the console UI. Locally, the same route opens .bb-data/settings.json, where secret values live next to settings (plaintext, dev-only). One route, one place, local and deployed.

3. Well-known name + the aws CLI — for CI/CD-driven workflows

If your deployment model is scripted, give the secret an explicit, stable name and set/rotate it with the standard AWS CLI. The value travels machine → Secrets Manager over the AWS API; it never enters source, git, or the CloudFormation template.

const stripeKey = new Secret(scope, 'stripe-key', { name: 'my-app/stripe-key' });
# Set / rotate — value over stdin, never argv or shell history.
printf '%s' "$STRIPE_KEY" \
  | aws secretsmanager put-secret-value \
      --secret-id my-app/stripe-key \
      --secret-string file:///dev/stdin \
      --region us-east-1

# Inspect (metadata only; the value requires explicit get-secret-value perms).
aws secretsmanager describe-secret --secret-id my-app/stripe-key

The Blocks app creates the secret (empty/random) when you deploy, so the value is set against a secret that already exists — run the CLI once the secret exists. The exact sequencing in your pipeline is up to you. Re-running put-secret-value overwrites (rotation); running instances pick up the new value once their cached copy expires (cacheTtlSeconds, default 300 s). Write to the same region the app deploys to, or reads see an unset secret.

Without an explicit name, the secret name is derived from the scope tree; find it in the settings resource group, the console, or aws secretsmanager list-secrets.

Rotating & removing

  • Rotate: set a new value (console or put-secret-value); a warm runtime that already cached the old value picks up the change once its cache entry expires (cacheTtlSeconds, default 300 s) — no redeploy needed. (A put() from within the app clears that instance's cache immediately.)
  • Remove: aws secretsmanager delete-secret --secret-id <name> (recoverable by default; --force-delete-without-recovery to skip the recovery window).

Which approach, when

ScenarioApproach
Centrally-managed credential shared across many app installationsfromExisting(arn) — reference it; the app never owns or sets it
A normal app, set the value once after deploySettings endpoint → console (approach 2)
Scripted / CI/CD-driven provisioningWell-known name + aws CLI (approach 3)
An opaque value nobody needs to readAuto-generated (approach 1)

There is intentionally no way to seed a secret's value from source code or an environment variable through the constructor — a literal in source would land in git and the template, and env-seeding is kept out of the declaration (consistent with how hosting secret() separates declare from set). Set values out-of-band via the console or CLI.

Reading an earlier version (rotation grace window)

Each change keeps the prior value available as the previous version. This is useful during a key rollout: validate against both the new and the old value for a grace period.

const current  = await signingKey.get();                       // live value
const previous = await signingKey.get({ version: 'previous' }); // value before the last change, or null
// verify(token, current) || (previous !== null && verify(token, previous))

get({ version: 'previous' }) returns null until the secret has been changed at least once. To browse the retained versions (metadata only — never values), use listVersions() and pass a returned item straight back to get():

const versions = await signingKey.listVersions();
// [{ versionId, stages: ['AWSCURRENT'], createdDate }, { versionId, stages: ['AWSPREVIOUS'], createdDate }]
const value = await signingKey.get({ version: versions[1] });  // read a specific version

For anything deeper than the current/previous pair (full history, custom staging labels, reading by an arbitrary id), use the AWS SDK directly against the secret — the BB covers the common path and leaves the raw resource reachable. The secret's ARN is exposed on the CDK construct (secret.secretArn, available at synth) — pass it where you build the SDK client; the runtime/mock classes deliberately don't expose AWS primitives.

Scaling & cost (AWS)

  • Billing: $0.40 per secret per month (prorated hourly) + $0.05 per 10,000 API calls. No free tier.
  • Latency: low-tens-of-ms per API call; the runtime caches the fetched value in-process, so steady-state reads hit the cache, not the API. A cached value is held for cacheTtlSeconds — default 5 minutes (matching the AWS Secrets Manager agent) — so a rotated value is picked up within the TTL even on a long-running process; raise it to cut fetches, or set 0 to disable caching.
  • Throughput: GetSecretValue scales to thousands of requests/sec per account (default quotas, raisable). Reads are the hot path; put() is rare.
  • Value size limit: 64 KB per secret value.
  • Version history: listVersions() returns the labeled versions — current + previous. Secrets Manager retains additional unlabeled versions internally (until >100, never removing any created in the last 24h), but the BB surfaces only current/previous; reach for the AWS SDK for deeper history.
  • Encryption: KMS at rest, using the default aws/secretsmanager key.
  • IAM: the construct grants the shared execution role secretsmanager:GetSecretValue + ListSecretVersionIds scoped to the single secret (not a wildcard). PutSecretValue is granted only when allowRuntimeWrite: true — the default is read-only. KMS grants Secrets Manager attaches as needed.
  • Deletion: recoverable by default (30-day recovery window); --force-delete-without-recovery to delete immediately.
  • Removal policy follows the stack-wide defaults (production retains, sandbox destroys); override per-instance with removalPolicy. BB-created secrets are tagged aws-blocks-stack=<stackName> (join the settings resource group); fromExisting secrets are not tagged.

Local development

The mock stores the current value as plaintext in the shared .bb-data/settings.json (the same file AppSetting uses, keyed by the secret's name or fullId) and logs a one-time warning. This is why the local /aws-blocks/settings route surfaces secrets alongside settings. The previous value (for get({ version: 'previous' }) / listVersions()) is kept in a small .bb-data/secret-versions.json sidecar — current + previous only; deeper history is an AWS-only capability. It is not secure — never use real credentials in local development. Wipe with rm -rf .bb-data.

Secret runs server-side only. Importing and instantiating it in browser/client code throws SecretErrors.NotSupported — a secret read in the browser would expose it to the client.

版本与发布记录

@aws-blocks/bb-secret 共发布 3 个版本,首次发布于 2026/10/16;过去 12 个月发布了 3 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.3.0latest2026/11/025 天前
0.2.02026/10/1622 天前
0.0.0-stage2026/10/1622 天前

关于 @aws-blocks/bb-secret 的常见问题

@aws-blocks/bb-secret 是什么?

A single application secret backed by AWS Secrets Manager.

如何安装 @aws-blocks/bb-secret?

在项目目录运行 npm install @aws-blocks/bb-secret,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:constructs(^10.6.0)、aws-cdk-lib(^2.257.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@aws-blocks/bb-secret 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@aws-blocks/bb-secret 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@aws-blocks/bb-secret 可以商用吗?

@aws-blocks/bb-secret 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@aws-blocks/bb-secret 的下载量有多少?

根据 npm 官方下载统计,@aws-blocks/bb-secret 在 2026/10/07 – 2026/11/05 这 30 天内被下载 41,064 次,日均约 1,369 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@aws-blocks/bb-secret 还在维护吗?

最新版本 0.3.0 发布于 2026/11/02(5 天前);至今共发布 3 个版本,其中过去 12 个月发布了 3 个;首次发布于 2026/10/16。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。