Secrets Manager
Every credential the platform uses comes from AWS Secrets Manager at runtime. Nothing is baked into an environment variable, a container image, or a template.
There are two secrets, and the two halves of the platform read different ones.
| Secret | Created by | Read by | Referenced as |
|---|---|---|---|
|
SAM, as |
All five Lambda functions |
|
|
Nothing — pre-existing |
The AgentCore runtime |
|
|
This split is easy to miss. Adding a key to The CDK secret’s ARN is a hardcoded literal in |
The exact key list for agentcore-configs is in
Configuration and secrets.
The SAM resource
AgentcoreSecret:
Type: AWS::SecretsManager::Secret
Properties:
Name: !Sub "agentcore-configs"
Description: Shared API credentials for the agentcore pipeline
No GenerateSecretString, no SecretString. SAM creates the secret empty.
|
Deploying gives you a secret with no value in it. Every function will then fail at cold start when it tries to parse the payload. Populating it is a manual post-deploy step, and it is the single most common reason a fresh environment does not work:
Delete the local file afterwards. Never commit it — |
Keeping the value out of the template is what allows the template to be public and reviewable. The consequence is that the secret’s contents are not versioned anywhere, so a key added by one person is invisible to everyone else until something breaks. When you add one, document it in Configuration and secrets in the same pull request.
Loading in a Lambda
from functools import lru_cache
@lru_cache(maxsize=1)
def load_secrets() -> Secrets:
import boto3
client = boto3.client("secretsmanager")
raw = client.get_secret_value(SecretId=os.environ["AGENTCORE_SECRET_ARN"])
return Secrets.model_validate_json(raw["SecretString"])
Three things are load-bearing here:
@lru_cache(maxsize=1)-
One Secrets Manager API call per container lifetime, not per invocation. A warm container that handles a thousand messages makes one call. Secrets Manager charges per API call and throttles, so an uncached fetch on a queue consumer at concurrency 10 is both a cost and a rate-limit problem.
- The deferred
import boto3 -
Nothing is imported at module load, so a test can import the module and patch
load_secrets()without AWS or the layer being present. See Testing Lambdas. - Pydantic validation
-
The payload is parsed into a typed model, so a missing key fails at cold start with a clear validation error rather than as a
KeyErrordeep inside a handler an hour later.
|
The cache is per container, so rotating a secret does not take effect until every warm container is recycled. There is no cache invalidation and no rotation configured. To force a pickup immediately, publish a trivial function configuration change — updating an environment variable replaces all containers. |
The call order at cold start is fixed: configure_sentry() first, then load_secrets(). If
loading the secret throws, Sentry is already initialised to report it.
Loading in the AgentCore runtime
agentcore/config/loader.py exposes load_config(), which fetches SECRET_ARN, validates it
with Pydantic, and injects the values into the process environment before the workflow registry
is built. It runs once, from main.py, before app.run().
The injection step exists because third-party SDKs read their own environment variables —
an Anthropic client wants ANTHROPIC_API_KEY in os.environ, not passed as an argument. See
Configuration.
Permissions
| Principal | Grant |
|---|---|
|
|
The other four functions |
|
AgentCore runtime role |
|
enrichment-scraper is the only writer. It logs into SmartScout, receives a session token, and
writes it back so the next invocation reuses it instead of re-authenticating. That is a genuine
requirement, but it means one function can overwrite the entire shared secret — the widest
single permission in the platform.
The runtime role’s fallback to a wildcard resource only applies when secretArn is unset. It
is set in cdk/bin/cdk.ts, so the deployed policy is scoped to one ARN. Removing that prop
would silently widen the grant to every secret in the account. More in IAM.
Never in a secret
| Value | Where it belongs |
|---|---|
Model ids, prompts, provider choice |
Runtime code. Never in CDK, SAM, or a secret. |
|
A plain environment variable — it is an identifier, not a credential. |
Queue URLs, log group names, batch sizes |
Environment variables from the template. |
Watermarks, cursors, kill switches |
The |
The dividing line: a secret holds things that would cause harm if disclosed. Everything else is configuration, and configuration belongs somewhere reviewable.