secret-management — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited secret-management (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Secure handling of API keys, passwords, tokens, and sensitive configuration across development, CI/CD, and production environments. Never hardcode secrets; always use environment variables, secret managers, or vault solutions.
Use .env files for local development, never commit them to version control.
# .env (NEVER committed to git)
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
OPENAI_API_KEY=sk-proj-xxxxxxxxxxxxx
JWT_SECRET=your-256-bit-secret-here
REDIS_URL=redis://localhost:6379
# .env.example (committed to git, no real values)
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
OPENAI_API_KEY=sk-proj-your-key-here
JWT_SECRET=generate-a-secure-random-string
REDIS_URL=redis://localhost:6379# .gitignore - ALWAYS include these
.env
.env.local
.env.*.local
*.pem
*.key
credentials.json
service-account.jsonAlways validate that required secrets exist at startup, not at first use.
import { z } from 'zod';
import dotenv from 'dotenv';
dotenv.config();
const envSchema = z.object({
DATABASE_URL: z.string().url(),
OPENAI_API_KEY: z.string().startsWith('sk-'),
JWT_SECRET: z.string().min(32),
NODE_ENV: z.enum(['development', 'staging', 'production']),
PORT: z.coerce.number().default(3000),
});
function loadConfig() {
const result = envSchema.safeParse(process.env);
if (!result.success) {
const missing = result.error.issues.map(i => i.path.join('.'));
throw new Error(`Missing or invalid env vars: ${missing.join(', ')}`);
}
return result.data;
}
export const config = loadConfig();# Python equivalent with Pydantic
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
database_url: str
openai_api_key: str
jwt_secret: str
debug: bool = False
class Config:
env_file = ".env"
settings = Settings()Use Vault for production secret management with automatic rotation.
import Vault from 'node-vault';
const vault = Vault({
apiVersion: 'v1',
endpoint: process.env.VAULT_ADDR,
token: process.env.VAULT_TOKEN,
});
async function getSecret(path: string): Promise<string> {
try {
const result = await vault.read(`secret/data/${path}`);
return result.data.data.value;
} catch (error) {
throw new Error(`Failed to read secret at ${path}: ${error.message}`);
}
}
// Usage
const dbPassword = await getSecret('production/database');Configure secrets in CI/CD without exposing them in logs or artifacts.
# GitHub Actions - use repository secrets
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
API_KEY: ${{ secrets.API_KEY }}
run: |
# Never echo secrets
npm run deploy
# WRONG: This exposes secrets in logs
# - run: echo ${{ secrets.API_KEY }}
# WRONG: This exposes secrets in artifacts
# - run: env > debug.txt# GitLab CI - use protected variables
deploy:
stage: deploy
variables:
DATABASE_URL: $DATABASE_URL # Set in GitLab CI/CD settings
script:
- npm run deploy
only:
- mainImplement graceful secret rotation without downtime.
interface SecretProvider {
getCurrent(): Promise<string>;
getPrevious(): Promise<string | null>;
}
// Accept both current and previous secrets during rotation window
async function validateApiKey(
key: string,
provider: SecretProvider
): Promise<boolean> {
const current = await provider.getCurrent();
if (key === current) return true;
const previous = await provider.getPrevious();
if (previous && key === previous) return true;
return false;
}
// Rotate JWT secrets gracefully
function verifyToken(token: string): JwtPayload {
try {
return jwt.verify(token, config.JWT_SECRET) as JwtPayload;
} catch {
if (config.JWT_SECRET_PREVIOUS) {
return jwt.verify(token, config.JWT_SECRET_PREVIOUS) as JwtPayload;
}
throw new Error('Invalid token');
}
}const apiKey = "sk-...")| Context | Solution |
|---|---|
| Local development | .env files + dotenv, validated at startup |
| CI/CD pipelines | Platform secret storage (GitHub Secrets, GitLab Variables) |
| Production | Vault, AWS Secrets Manager, GCP Secret Manager |
| Rotation | Dual-key acceptance window, then revoke old key |
| Frontend | Never store secrets; proxy through backend API |
| Git protection | .gitignore, pre-commit hooks, git-secrets scanner |
| Validation | Zod/Pydantic schema at app startup |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.