From prodsec-skills
Evaluates APIs, configurations, and interfaces for misuse-resistant design. Identifies footguns, dangerous defaults, and patterns that enable security mistakes.
How this skill is triggered — by the user, by Claude, or both
Slash command
/prodsec-skills:sharp-edgesThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Evaluates whether APIs, configurations, and interfaces are resistant to developer misuse. Identifies designs where the "easy path" leads to insecurity.
Evaluates whether APIs, configurations, and interfaces are resistant to developer misuse. Identifies designs where the "easy path" leads to insecurity.
The pit of success: Secure usage should be the path of least resistance. If developers must understand cryptography, read documentation carefully, or remember special rules to avoid vulnerabilities, the API has failed.
| Rationalization | Why It's Wrong | Required Action |
|---|---|---|
| "It's documented" | Developers don't read docs under deadline pressure | Make the secure choice the default or only option |
| "Advanced users need flexibility" | Flexibility creates footguns; most "advanced" usage is copy-paste | Provide safe high-level APIs; hide primitives |
| "It's the developer's responsibility" | Blame-shifting; you designed the footgun | Remove the footgun or make it impossible to misuse |
| "Nobody would actually do that" | Developers do everything imaginable under pressure | Assume maximum developer confusion |
| "It's just a configuration option" | Config is code; wrong configs ship to production | Validate configs; reject dangerous combinations |
| "We need backwards compatibility" | Insecure defaults can't be grandfather-claused | Deprecate loudly; force migration |
APIs that let developers choose algorithms invite choosing wrong ones.
The JWT Pattern (canonical example):
"alg": "none" to bypass signaturesDetection patterns:
algorithm, mode, cipher, hash_typeExample - PHP password_hash allowing weak algorithms:
// DANGEROUS: allows crc32, md5, sha1
password_hash($password, PASSWORD_DEFAULT); // Good - no choice
hash($algorithm, $password); // BAD: accepts "crc32"
Defaults that are insecure, or zero/empty values that disable security.
The OTP Lifetime Pattern:
# What happens when lifetime=0?
def verify_otp(code, lifetime=300): # 300 seconds default
if lifetime == 0:
return True # OOPS: 0 means "accept all"?
# Or does it mean "expired immediately"?
Detection patterns:
Questions to ask:
timeout=0? max_attempts=0? key=""?APIs that expose raw bytes instead of meaningful types invite type confusion.
The Libsodium vs. Halite Pattern:
// Libsodium (primitives): bytes are bytes
sodium_crypto_box($message, $nonce, $keypair);
// Easy to: swap nonce/keypair, reuse nonces, use wrong key type
// Halite (semantic): types enforce correct usage
Crypto::seal($message, new EncryptionPublicKey($key));
// Wrong key type = type error, not silent failure
Detection patterns:
bytes, string, []byte for distinct security conceptsThe comparison footgun:
// Timing-safe comparison looks identical to unsafe
if hmac == expected { } // BAD: timing attack
if hmac.Equal(mac, expected) { } // Good: constant-time
// Same types, different security properties
One wrong setting creates catastrophic failure, with no warning.
Detection patterns:
Examples:
# One typo = disaster
verify_ssl: fasle # Typo silently accepted as truthy?
# Magic values
session_timeout: -1 # Does this mean "never expire"?
# Dangerous combinations accepted silently
auth_required: true
bypass_auth_for_health_checks: true
health_check_path: "/" # Oops
// Sensible default doesn't protect against bad callers
public function __construct(
public string $hashAlgo = 'sha256', // Good default...
public int $otpLifetime = 120, // ...but accepts md5, 0, etc.
) {}
Deeper configuration footguns (zero/empty semantics, boolean traps, unvalidated constructors): Inlined: config patterns below.
Errors that don't surface, or success that masks failure.
Detection patterns:
Examples:
# Silent bypass
def verify_signature(sig, data, key):
if not key:
return True # No key = skip verification?!
# Return value ignored
signature.verify(data, sig) # Throws on failure
crypto.verify(data, sig) # Returns False on failure
# Developer forgets to check return value
Security-critical values as plain strings enable injection and confusion.
Detection patterns:
The permission accumulation footgun:
permissions = "read,write"
permissions += ",admin" # Too easy to escalate
# vs. type-safe
permissions = {Permission.READ, Permission.WRITE}
permissions.add(Permission.ADMIN) # At least it's explicit
For each choice point, ask:
0, "", null, []?-1 mean? Infinite? Error?Consider three adversaries:
The Scoundrel: Actively malicious developer or attacker controlling config
The Lazy Developer: Copy-pastes examples, skips documentation
The Confused Developer: Misunderstands the API
For each identified sharp edge:
If a finding seems questionable, return to Phase 2 and probe more edge cases.
| Severity | Criteria | Examples |
|---|---|---|
| Critical | Default or obvious usage is insecure | verify: false default; empty password allowed |
| High | Easy misconfiguration breaks security | Algorithm parameter accepts "none" |
| Medium | Unusual but possible misconfiguration | Negative timeout has unexpected meaning |
| Low | Requires deliberate misuse | Obscure parameter combination |
Per-category and per-language guides (crypto-apis, auth-patterns, case studies, lang-*.md): (see upstream Trail of Bits prodsec-skills for companion files) — skills/sharp-edges/references/.
Before concluding analysis:
references/config-patterns.md)Dangerous configuration patterns that enable security failures.
# What does 0 mean?
session_timeout: 0 # Infinite timeout? Immediate expiry? Disabled?
token_lifetime: 0 # Never expires? Already expired? Use default?
max_attempts: 0 # No attempts allowed? Unlimited attempts?
Real-world failures:
lifetime=0 means "accept any OTP regardless of age"max_attempts=0 disables rate limitingtimeout=0 means "session never expires"Detection: Any numeric security parameter that accepts 0.
Fix: Explicit constants, validation, or separate enable/disable flag.
# BAD
def verify_otp(code: str, lifetime: int = 300):
if lifetime <= 0:
return True # What??
# GOOD
def verify_otp(code: str, lifetime: int = 300):
if lifetime <= 0:
raise ValueError("lifetime must be positive")
# Passwords
if user_password == stored_hash: # What if stored_hash is ""?
# API keys
if api_key == config.api_key: # What if config is empty?
grant_access()
# The empty string equals the empty string
"" == "" # True - authentication bypassed
Detection: String comparisons for authentication without empty checks.
// DANGEROUS: null means "skip verification"
function verifySignature(data, signature, publicKey) {
if (!publicKey) return true; // No key = trust everything?
return crypto.verify(data, signature, publicKey);
}
// DANGEROUS: null means "any value"
function checkRole(user, requiredRole) {
if (!requiredRole) return true; // No requirement = allow all?
return user.roles.includes(requiredRole);
}
# Every one of these has caused real vulnerabilities
verify_ssl: false
validate_certificate: false
check_signature: false
require_auth: false
enable_csrf_protection: false
sanitize_input: false
Full file includes unvalidated constructor parameters, dangerous combos, and remediation patterns: (see upstream Trail of Bits prodsec-skills for companion files).
npx claudepluginhub redhatproductsecurity/prodsec-skills --plugin prodsec-skillsAnalyzes API design, configuration schemas, and interfaces to identify footguns that enable security misuse. Use when reviewing authentication, cryptography, or any security-relevant developer choices.
Analyzes APIs, configurations, and interfaces for footgun designs that enable security mistakes, ensuring secure-by-default ergonomics.
Identifies error-prone APIs, dangerous configurations, and footgun designs enabling security mistakes. Reviews API designs, config schemas, crypto ergonomics for secure-by-default principles.