| name | dsg-secrets-manager-integrator |
| version | 1.0.0 |
| author | DSG Team |
| license | MIT |
| description | Integrate DSG agents and services with AWS Secrets Manager for production-grade secret management. Configure credential broker for Secrets Manager queries, create IAM policies for secret access, updat |
DSG Secrets Manager Integrator
Production secrets management via AWS Secrets Manager integration with DSG agents, credential broker, Hermes controlled executor, and deterministic governance.
When to invoke this skill
| Intent | Use this skill |
|---|
| "Configure credential broker for Secrets Manager" | โ
Yes โ broker integration |
| "Set up Hermes executor with secret leasing" | โ
Yes โ controlled executor auth |
| "Remove hardcoded secrets from environment" | โ
Yes โ migration to vault |
| "Rotate API keys and database passwords" | โ
Yes โ key rotation |
| "Audit which service accessed which secret" | โ
Yes โ access audit trail |
| "Test credential lease expiration" | โ
Yes โ lease lifecycle |
| "Create IAM policy for secret access" | โ
Yes โ policy generation |
| "Enable ECS task to query Secrets Manager" | โ
Yes โ ECS integration |
| "Quick health check on a single secret" | โ Use AWS CLI or Console directly |
Architecture overview
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ DSG Agent / Service โ
โโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Credential Broker (Node.js) โ
โ lib/dsg/brain/credential-broker.ts โ
โ โ
โ โ Query Secrets Manager โ
โ โ Issue leases with expiration โ
โ โ Return fingerprints (not raw secrets) โ
โ โ Audit access in DynamoDB โ
โ โ Support policy-based grants โ
โโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโ
โผ โผ โผ
AWS Secrets Manager DynamoDB CloudTrail
โโโโโโโโโโโโโโโโโโโ (credential_leases) (audit logs)
โ api-secrets-v2 โ
โ - API_KEY โ
โ - JWT_SECRET โ
โ โ
โ db-secrets-v2 โ
โ - password โ
โ - host โ
โ โ
โ oauth-secrets-v2โ
โ - client_id โ
โ - client_secret โ
โโโโโโโโโโโโโโโโโโโ
Secrets Manager structure
Pre-deployment secrets (created by CDK)
aws secretsmanager get-secret-value --secret-id dsg-one-dev-api-secrets-v2
aws secretsmanager get-secret-value --secret-id dsg-one-dev-database-secrets-v2
aws secretsmanager get-secret-value --secret-id dsg-one-dev-oauth-secrets-v2
Credential broker schema (DynamoDB)
CREATE TABLE credential_leases (
id TEXT PRIMARY KEY,
secret_name TEXT NOT NULL,
secret_version TEXT,
requester_id TEXT NOT NULL,
grant_policy_hash TEXT,
lease_issued_at TIMESTAMP NOT NULL,
lease_expires_at TIMESTAMP NOT NULL,
credential_fingerprint TEXT NOT NULL,
access_log_id TEXT,
status TEXT NOT NULL,
revoked_at TIMESTAMP,
revoke_reason TEXT
);
CREATE TABLE credential_access_audit (
id TEXT PRIMARY KEY,
timestamp TIMESTAMP NOT NULL,
requester_id TEXT NOT NULL,
secret_name TEXT NOT NULL,
action TEXT NOT NULL,
outcome TEXT NOT NULL,
denial_reason TEXT,
gate_policy_hash TEXT,
fingerprint_returned TEXT,
request_context TEXT
);
Credential broker implementation
Current state (Supabase-backed)
lib/dsg/brain/credential-broker.ts currently queries:
async function getSecret(secretName: string, policyHash: string) {
const gateStatus = await evaluateGate(policyHash);
if (gateStatus !== "PASS") throw new Error("Gate denied");
const secret = await supabase
.from("dsg_secrets")
.select("*")
.eq("name", secretName)
.single();
const fingerprint = sha256(secret.value);
const lease = await createLease(secretName, fingerprint);
await auditAccess("GET", secretName, "SUCCESS", lease.id);
return { fingerprint, leaseId: lease.id, expiresAt: lease.expiresAt };
}
Target: Secrets Manager migration
async function getSecret(secretName: string, policyHash: string) {
const gateStatus = await evaluateGate(policyHash);
if (gateStatus !== "PASS") throw new Error("Gate denied");
const secretsManager = new SecretsManagerClient({ region: "us-east-1" });
const secret = await secretsManager.send(
new GetSecretValueCommand({ SecretId: secretName })
);
const secretValue = JSON.parse(secret.SecretString || "");
const fingerprint = sha256(JSON.stringify(secretValue));
const lease = await createLease(secretName, fingerprint);
await auditAccess("GET", secretName, "SUCCESS", lease.id, policyHash);
return {
fingerprint,
leaseId: lease.id,
expiresAt: lease.expiresAt,
version: secret.VersionId,
};
}
Integration patterns
Pattern 1: Direct service authentication
const secretsManager = new SecretsManagerClient({ region: "us-east-1" });
async function getApiKey() {
try {
const response = await secretsManager.send(
new GetSecretValueCommand({
SecretId: "dsg-one-prod-api-secrets-v2",
})
);
const secrets = JSON.parse(response.SecretString || "{}");
return secrets.ANTHROPIC_API_KEY;
} catch (error) {
console.error("Failed to fetch secret:", error);
throw error;
}
}
Pattern 2: Agent credential leasing (via broker)
const broker = new CredentialBroker({ backendUrl: "..." });
async function executePlan(plan: Plan) {
const gateResult = await broker.evaluateGate(plan, policyHash);
if (gateResult.status !== "PASS") throw new Error("Gate denied");
const lease = await broker.leaseCredential(
"dsg-one-prod-api-secrets-v2",
gateResult.proofHash,
{ expirySeconds: 900 }
);
console.log(`Using credential (fingerprint: ${lease.fingerprint})`);
}
Pattern 3: Hermes controlled executor
const executor = new HermesExecutor({ credentialBroker: broker });
async function executeControlledPlan(plan: Plan) {
}
Setup: Step-by-step
Step 1: IAM Policy for ECS Task Execution Role
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "SecretsManagerReadAccess",
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret",
"secretsmanager:ListSecretVersionIds"
],
"Resource": [
"arn:aws:secretsmanager:us-east-1:ACCOUNT_ID:secret:dsg-one-dev-*"
]
},
{
"Sid": "KmsDecryptAccess",
"Effect": "Allow",
"Action": ["kms:Decrypt", "kms:DescribeKey"],
"Resource": [
"arn:aws:kms:us-east-1:ACCOUNT_ID:key/KMS_KEY_ID"
]
},
{
"Sid": "DynamoDBAccessForAudit",
"Effect": "Allow",
"Action": [
"dynamodb:PutItem",
"dynamodb:Query",
"dynamodb:Scan"
],
"Resource": [
"arn:aws:dynamodb:us-east-1:ACCOUNT_ID:table/credential_leases",
"arn:aws:dynamodb:us-east-1:ACCOUNT_ID:table/credential_access_audit"
]
}
]
}
Step 2: Update ECS Task Definition
{
"containerDefinitions": [
{
"name": "dsg-api",
"image": "ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/dsg-api:latest",
"secrets": [
{
"name": "ANTHROPIC_API_KEY",
"valueFrom": "arn:aws:secretsmanager:us-east-1:ACCOUNT_ID:secret:dsg-one-prod-api-secrets-v2:ANTHROPIC_API_KEY::"
},
{
"name": "DB_PASSWORD",
"valueFrom": "arn:aws:secretsmanager:us-east-1:ACCOUNT_ID:secret:dsg-one-prod-database-secrets-v2:password::"
}
],
"environment": [
{
"name": "CREDENTIAL_BROKER_URL",
"value": "http://localhost:3001/api/credential-broker"
},
{
"name": "SECRETS_MANAGER_ENABLED",
"value": "true"
}
]
}
],
"executionRoleArn": "arn:aws:iam::ACCOUNT_ID:role/ecsTaskExecutionRole"
}
Step 3: Update credential broker (Node.js)
import {
SecretsManagerClient,
GetSecretValueCommand,
} from "@aws-sdk/client-secrets-manager";
const secretsManager = new SecretsManagerClient({ region: "us-east-1" });
export async function getSecretFromVault(
secretName: string,
version?: string
): Promise<Record<string, string>> {
try {
const response = await secretsManager.send(
new GetSecretValueCommand({
SecretId: secretName,
VersionId: version,
})
);
if (response.SecretString) {
try {
return JSON.parse(response.SecretString);
} catch {
return { value: response.SecretString };
}
} else if (response.SecretBinary) {
return { value: Buffer.from(response.SecretBinary).toString("utf-8") };
}
throw new Error("Secret has no value");
} catch (error) {
console.error(`Failed to retrieve secret ${secretName}:`, error);
throw error;
}
}
export async function issueLease(
secretName: string,
policyHash: string,
options?: { expirySeconds?: number }
) {
const secret = await getSecretFromVault(secretName);
const fingerprint = sha256(JSON.stringify(secret));
const expirySeconds = options?.expirySeconds || 900;
const lease = {
id: generateUUID(),
secretName,
fingerprint,
issuedAt: new Date(),
expiresAt: new Date(Date.now() + expirySeconds * 1000),
status: "ACTIVE",
};
await dynamodb
.from("credential_leases")
.insert([
{
id: lease.id,
secret_name: secretName,
requester_id: getRequesterId(),
grant_policy_hash: policyHash,
credential_fingerprint: fingerprint,
lease_expires_at: lease.expiresAt,
status: "ACTIVE",
},
]);
await auditSecretAccess("LEASE", secretName, "SUCCESS", lease.id, policyHash);
return {
leaseId: lease.id,
fingerprint,
expiresAt: lease.expiresAt,
};
}
Step 4: Enable in application
import { getSecretFromVault } from "@/lib/dsg/brain/credential-broker";
export async function POST(request: Request) {
const { agentId, plan } = await request.json();
const gateResult = await evaluateGate(plan);
if (gateResult.status !== "PASS") {
return NextResponse.json({ status: "BLOCK" }, { status: 403 });
}
if (plan.requiresCredential) {
const lease = await issueLease(plan.credentialName, gateResult.proofHash);
console.log(`Credential lease: ${lease.fingerprint}`);
}
}
Security best practices
โ
DO
- Store secrets in Secrets Manager (never hardcoded)
- Issue leases with short expiry (15 min default)
- Return fingerprints to callers (never raw secrets)
- Audit every secret access (who, what, when, why)
- Use IAM roles (not API keys) for ECS task authentication
- Rotate keys on schedule (90 days minimum)
- Use KMS encryption for Secrets Manager
- Enable CloudTrail for secret access audit trail
- Test credential broker under load
- Monitor lease expiration and renewal
โ DON'T
- Hardcode secrets in environment variables
- Print secrets in logs (use fingerprints instead)
- Return raw secrets to untrusted code
- Allow unlimited lease duration
- Skip audit logging
- Use root credentials in applications
- Store backup secrets in unencrypted files
- Ignore secret rotation deadlines
- Bypass the credential broker for "quick access"
- Grant overly broad IAM permissions
Testing & verification
Test 1: Secrets Manager connectivity
aws secretsmanager get-secret-value \
--secret-id dsg-one-prod-api-secrets-v2 \
--region us-east-1
Test 2: Credential broker lease issuance
const lease = await issueLease("dsg-one-prod-api-secrets-v2", policyHash);
expect(lease.fingerprint).toBeDefined();
expect(lease.leaseId).toBeDefined();
expect(lease.expiresAt.getTime() > Date.now()).toBe(true);
Test 3: Lease expiration
const lease = await issueLease("...", policyHash, {
expirySeconds: 1,
});
await sleep(2000);
const result = await useLease(lease.leaseId);
expect(result.status).toBe("EXPIRED");
Test 4: Audit trail
const auditRecords = await queryAudit({
secretName: "dsg-one-prod-api-secrets-v2",
action: "LEASE",
status: "SUCCESS",
});
expect(auditRecords.length).toBeGreaterThan(0);
expect(auditRecords[0].fingerprint).toBeDefined();
expect(auditRecords[0].requester_id).toBeDefined();
Rollback procedure
If Secrets Manager integration causes issues:
aws ecs update-service \
--cluster dsg-one-prod \
--service dsg-service \
--force-new-deployment
Maintenance & monitoring
Key metrics to track
- Lease issuance rate (leases/min) โ should be < 10/min for most services
- Lease expiration rate (expirations/min) โ should match or exceed issuance
- Access denial rate (denials/day) โ investigate any denial spikes
- Secret rotation lag (days since last rotation) โ should be < 90
- Broker latency (milliseconds) โ p99 < 100ms
Scheduled tasks
- Daily: Review credential access audit logs
- Weekly: Check lease expiration health
- Monthly: Rotate long-lived API keys
- Quarterly: Review and update IAM policies
- Annually: Audit complete credential lifecycle