Your agent pipeline reads its secrets from two different places depending on where it runs: a .env file on your laptop, and Google Secret Manager in production. One pipeline, two sources, and a single if to switch between them.
var builder = WebApplication.CreateBuilder(args);
if (builder.Environment.IsProduction())
builder.Configuration.AddGcpSecretManager(projectId); // mounted at runtime
else
DotNetEnv.Env.Load(); // laptop only — never shipped
// From here on, code reads IConfiguration and never knows the difference:
var bullhornSecret = builder.Configuration["Bullhorn:ClientSecret"];Dev convenience and production safety should cost you one
if, not two codebases.
The runtime service account gets the smallest set of permissions that lets the agents do their job, and nothing more. If the container is ever compromised, this is the blast radius — so keep it small.
Grant only these roles to agents-runtime@…:
# Read only the specific secrets it needs — not "all secrets"
gcloud secrets add-iam-policy-binding openai-api-key \
--member serviceAccount:agents-runtime@$GCP_PROJECT_ID.iam.gserviceaccount.com \
--role roles/secretmanager.secretAccessor
# ...repeat per-secret. Bind at the secret level, never project-wide.
# Pull work from the queue; write to the DLQ
gcloud projects add-iam-policy-binding $GCP_PROJECT_ID \
--member serviceAccount:agents-runtime@$GCP_PROJECT_ID.iam.gserviceaccount.com \
--role roles/pubsub.subscriber
# Write structured logs and traces
# roles/logging.logWriter, roles/cloudtrace.agentThe rules, in plain terms:
secretAccessor on each individual secret. A blanket "read all secrets" grant means one compromised container reads everything.write_note scope), nothing else.Least privilege isn't paranoia. It's deciding the size of tomorrow's incident, today.
secretsmanager:GetSecretValue on named secrets), SQS ReceiveMessage/DeleteMessage on the one queue, and CloudWatch PutLogEvents. Deploy via a separate IAM principal.get/list on named secrets only (via access policy or RBAC Key Vault Secrets User), Service Bus Receiver on the one queue, and the Monitoring Metrics/Logs publisher roles.The principle survives the platform unchanged: the running service holds the fewest keys that let it do exactly its job — and it cannot deploy itself, read secrets it doesn't use, or reach data it has no business touching.
See also: the endpoint cheat-sheets, with the exact Bullhorn and JobAdder routes, auth flows, and fields the agents actually call.
Download the full PDF for free?