Skip to content

Secrets

Slingshot resolves all secrets at startup. A server that boots successfully has all its secrets. A server that can’t find a required secret throws before accepting a single request.

The JWT signing key is the only always-required secret. Everything else depends on which packages and features you enable.

SecretUsed byNotes
signing.secretslingshot-authJWT signing. Minimum 32 characters. Rotate via key versioning.
SecretUsed when
DATABASE_URLPostgres adapter is configured
MONGO_URLMongoDB adapter is configured
REDIS_URLRedis adapter is configured (sessions, cache, queues)
SecretUsed when
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRETGoogle OAuth is enabled
GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRETGitHub OAuth is enabled
APPLE_CLIENT_ID / APPLE_TEAM_ID / APPLE_KEY_ID / APPLE_PRIVATE_KEYApple Sign-In is enabled
SMTP_HOST / SMTP_USER / SMTP_PASSEmail delivery via SMTP
SecretPackageUsed when
SCIM_BEARER_TOKENslingshot-scimSCIM provisioning is enabled
MEILI_API_KEYslingshot-searchMeilisearch adapter
ES_API_KEYslingshot-searchElasticsearch adapter
TYPESENSE_API_KEYslingshot-searchTypesense adapter
SecretUsed when
AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEYDeploying to AWS (ECS, RDS, SSM)
CLOUDFLARE_API_TOKENDNS via Cloudflare
DOCKER_REGISTRY_TOKENPushing to a private Docker registry

Set them in your shell or deployment environment:

Terminal window
export SECRET=my-32-char-minimum-signing-secret
export DATABASE_URL=postgresql://user:pass@host/db

Read them directly inside app.config.ts:

app.config.ts (excerpt)
import { defineApp } from '@lastshotlabs/slingshot';
export default defineApp({
security: { signing: { secret: process.env.SECRET! } },
db: { postgres: process.env.DATABASE_URL },
});

Use a .env file for local development by configuring the file-backed secrets provider on secrets:

app.config.ts (excerpt)
import { defineApp } from '@lastshotlabs/slingshot';
export default defineApp({
secrets: {
provider: 'file',
path: `${import.meta.dir}/.env`,
},
});
.env
SECRET=my-32-char-minimum-signing-secret
DATABASE_URL=postgresql://localhost/myapp
REDIS_URL=redis://localhost:6379

Store production secrets in SSM:

app.config.ts (excerpt)
export default defineApp({
secrets: {
provider: 'ssm',
pathPrefix: '/myapp/prod/',
region: 'us-east-1',
},
});

SSM parameters fetch at startup. /myapp/prod/SECRET resolves as SECRET, /myapp/prod/DATABASE_URL as DATABASE_URL, and so on.

Push secrets to SSM with the CLI:

Terminal window
slingshot secrets push --stage prod

Pull them back to a local .env:

Terminal window
slingshot secrets pull --stage prod

Validate all required secrets are present before deploying:

Terminal window
slingshot secrets check --stage prod

Reads your app.config.ts, determines which packages and features are enabled, and verifies that every required secret exists in the configured provider. Missing secrets are listed by name — no hunting through stack traces.


JWT signing uses signing.secret to sign and verify tokens. Rotating the secret invalidates all existing sessions.

For zero-downtime rotation, configure a key version:

app.config.ts (excerpt)
export default defineApp({
security: {
signing: {
secret: process.env.SECRET_V2!,
previousSecrets: [process.env.SECRET_V1!],
},
},
});

Tokens signed with SECRET_V1 remain valid until they expire. New tokens sign with SECRET_V2. Once all V1 tokens have expired, remove previousSecrets.


app.config.ts is a TypeScript module: read straight from process.env, or call out to the configured secrets store via the helpers exposed in @lastshotlabs/slingshot. Most apps just read process.env directly and let the secrets provider populate it from SSM / a file at boot.

app.config.ts (excerpt)
export default defineApp({
security: {
signing: { secret: process.env.SIGNING_SECRET! },
},
db: {
postgres: process.env.DATABASE_URL,
redis: process.env.REDIS_URL,
},
});