| name | implementing-api-key-security-controls |
| description | Implements secure API key generation, storage, rotation, and revocation controls to protect API authentication credentials from leakage, brute force, and abuse. The engineer designs API key formats with sufficient entropy, implements secure hashing for storage, enforces per-key scoping and rate limiting, monitors for leaked keys in public repositories, and builds key rotation workflows. Activates for requests involving API key management, API key security, key rotation policy, or API credential protection.
|
| domain | cybersecurity |
| subdomain | api-security |
| tags | ["api-security","api-keys","credential-management","key-rotation","secret-management"] |
| version | 1.0.0 |
| author | mahipal |
| license | Apache-2.0 |
| nist_ai_rmf | ["MEASURE-2.7","MAP-5.1","MANAGE-2.4"] |
| atlas_techniques | ["AML.T0070","AML.T0066","AML.T0082"] |
| nist_csf | ["PR.PS-01","ID.RA-01","PR.DS-10","DE.CM-01"] |
| mitre_attack | ["T1190","T1059.007","T1552.001","T1003","T1110"] |
| source | https://github.com/mukul975/Anthropic-Cybersecurity-Skills |
| source_commit | 04450304b12645cb2b974ab96d28c0664758a88d |
| note | Vendored verbatim from an external Apache-2.0 security-skill library, pinned by commit. Exceeds the internal 300-line skill guideline (agent-code-constraints.md) -- kept as-is because this is vendored reference material (forensics/threat-intel procedure), not Yana AI-authored content, and trimming would damage technical accuracy. |
Implementing API Key Security Controls
When to Use
- Designing secure API key generation with sufficient entropy and identifiable prefixes for leak detection
- Implementing server-side API key hashing (never storing keys in plaintext) with SHA-256 or bcrypt
- Building key rotation workflows that allow zero-downtime key replacement for API consumers
- Configuring per-key scoping to limit each API key to specific endpoints, IP ranges, and rate limits
- Setting up automated monitoring for API key leakage in GitHub repos, logs, and client-side code
Do not use API keys as the sole authentication mechanism for user-facing applications. API keys are best suited for server-to-server communication and developer access.
Prerequisites
- Secure random number generator (os.urandom, secrets module) for key generation
- Database with proper encryption at rest for storing hashed API keys
- Redis or similar store for key-to-metadata caching and rate limiting
- Secret scanning tools (GitHub secret scanning, truffleHog, gitleaks)
- Monitoring and alerting infrastructure for key usage anomalies
Workflow
Step 1: Secure API Key Generation
import secrets
import hashlib
import hmac
import time
import json
from datetime import datetime, timedelta
class APIKeyManager:
"""Manages secure API key lifecycle: generation, storage, validation, rotation."""
KEY_PREFIXES = {
"live_secret": "sk_live_",
"test_secret": "sk_test_",
"live_public": "pk_live_",
"test_public": "pk_test_",
}
def __init__(self, db_connection, redis_connection):
.db = db_connection
.redis = redis_connection
():
prefix = .KEY_PREFIXES.get(key_type, )
random_bytes = secrets.token_bytes()
key_body = secrets.token_urlsafe()
full_key =
key_hash = hashlib.sha256(full_key.encode()).hexdigest()
key_id =
key_metadata = {
: key_hash,
: key_id,
: key_type,
: owner_id,
: scopes [],
: rate_limit {: , : },
: ip_allowlist [],
: datetime.utcnow().isoformat(),
: (datetime.utcnow() + timedelta(days=expires_days)).isoformat(),
: ,
: ,
: ,
}
.db.execute(
,
(key_hash, key_id, json.dumps(key_metadata))
)
.redis.setex(
,
,
json.dumps(key_metadata)
)
{
: full_key,
: key_id,
: key_metadata[],
: key_metadata[],
}
():
key_hash = hashlib.sha256(api_key.encode()).hexdigest()
cached = .redis.get()
cached:
metadata = json.loads(cached)
:
row = .db.execute(
,
(key_hash,)
).fetchone()
row:
,
metadata = json.loads(row[])
.redis.setex(, , row[])
metadata.get():
,
metadata.get():
datetime.fromisoformat(metadata[]) < datetime.utcnow():
,
metadata[] = datetime.utcnow().isoformat()
metadata[] = metadata.get(, ) +
.redis.setex(, , json.dumps(metadata))
metadata,
():
row = .db.execute(
,
(key_id,)
).fetchone()
row:
key_hash = row[]
metadata = json.loads(row[])
metadata[] =
metadata[] = datetime.utcnow().isoformat()
.db.execute(
,
(json.dumps(metadata), key_id)
)
.redis.delete()
():
old_row = .db.execute(
,
(old_key_id,)
).fetchone()
old_row:
,
old_metadata = json.loads(old_row[])
new_key_data = .generate_key(
key_type=old_metadata[],
owner_id=old_metadata[],
scopes=old_metadata[],
rate_limit=old_metadata[],
ip_allowlist=old_metadata[],
)
revoke_at = datetime.utcnow() + timedelta(hours=grace_period_hours)
old_metadata[] = revoke_at.isoformat()
.db.execute(
,
(json.dumps(old_metadata), old_key_id)
)
{
: new_key_data,
: old_key_id,
: revoke_at.isoformat(),
:
},