SecureString
and injected into the container in production at startup.
Model
- Storage: AWS SSM Parameter Store, region
us-east-1, under the prefix/shared/prod/secrets/. TypeSecureString(encrypted with KMS, keyalias/aws/ssm). - Injection: AWS Copilot reads each parameter and delivers it to the
container as an environment variable (
process.env) when the ECS task starts. This happens automatically via thesecrets:block of the application manifest. - Permissions: the ECS task execution role has a policy
scoped to
ssm:GetParameters+kms:Decryptonly over the prefix/shared/prod/secrets/*. It cannot read other parameters in the account.
Auditability
- Every read of a parameter is recorded in AWS CloudTrail.
- Every change (
PutParameter,DeleteParameter) is recorded in CloudTrail as well, with the IAM identity of the author. - KMS logs
Decryptoperations separately in CloudTrail (data events if they are enabled).
Why not AWS Secrets Manager?
SSM Parameter Store SecureString offers the same KMS encryption as Secrets Manager and the same isolation guarantee. The main difference is the built-in automatic rotation (RDS / DocumentDB / Lambda), which we currently do not need. Choosing SSM saves us the per-secret cost of Secrets Manager (~$0.40/month each) while keeping the encryption and audit guarantees.What we do NOT do
- ❌ We never commit secrets to the repository. There is a
.dockerignorethat excludes.envand.env.*from the Docker image; local files never reach production. - ❌ We never pass secrets as inline arguments in CI/CLI. They end up in the shell history and in CI logs. They are exported to the environment before running the command that needs them.
- ❌ We never put secrets in the manifest
variables:(that section is in plain text inside the CloudFormation stack).
Environments
Current state: some local installations have production values mixed with sandbox inside theirIf you need a new secret in production, follow the procedure..env. The goal is for the local.envto contain only dev/sandbox values (local Mongo, Stytch sandbox project, Paddle sandbox, etc.) and for any connection to production resources to go exclusively through SSM when the container comes up in AWS.
Adding a new secret
-
Upload the value to SSM. Export the value in your shell and use the
helper that lives in the repo:
The script uploads every variable that is exported to
/shared/prod/secrets/<NOMBRE>asSecureString. It silently skips the ones that are not set. -
Reference it from the manifest in
copilot/woku-server/manifest.yml: -
Deploy. The next
copilot deploybrings up the container with the variable available inprocess.env.MI_SECRET_NUEVO.
Rotating a secret
For credentials without automatic rotation (everything we have today):- Generate the new value in the provider (e.g.: new
STYTCH_SECRETin the Stytch dashboard). aws ssm put-parameter --overwrite --type SecureString ...(the helper script already does this).- Force-redeploy the service so it picks up the new value:
copilot svc deploy --name woku-server. - Revoke the previous value in the provider.
The container does not detect changes in SSM live. The values are read only once at startup. That is why you must always redeploy after rotating.
Reporting a leak
If you discover an exposed secret (in a public repo, in logs, in a PR, in a screenshot), send the details toteam@woku.app. The
response process:
- Immediate rotation of the compromised value.
- Investigation of the previous value’s usage in CloudTrail.
- Customer notification if the rotation affects their integrations.