4.9 Review Secrets, Defaults, and Dangerous APIs
4.9 - Review Secrets, Defaults, and Dangerous APIs
Overview
These patterns are foot-guns and misplaced trust: secrets in source, obsolete APIs, dangerous primitives, weak framework defaults, and validation that exists only in the browser. Hunt for them as inventory passes, then prove impact.
The points below are the ideas this family chapter uses again and again.
- Secrets in source, dangerous dynamic APIs, weak defaults, and client-only validation expand blast radius.
- Prefer secret stores, safe APIs, and secure framework defaults.
- Client validation is UX; server enforcement is the control.
- Related variants share misplaced trust in code, config, or the browser.
- Evidence names the secret or API, missing control, impact, and a proving test.
After reading this chapter, we should be able to find hardcoded secrets, dangerous APIs, weak defaults, and client-only validation with shared habits.
Shared Review Model
Across every variant below, keep the same evidence habit: name the source, the sink, the missing control, the impact, and a test that would prove a fix.
Variants in This Family
Hardcoded secrets
Hardcoded secrets are credentials or cryptographic material stored in source code instead of a secret manager or environment configuration. Anyone with repository access—or an attacker who extracts strings from a binary or container image—may recover them. Hardcoded authentication bypasses are especially dangerous because they often survive code review as "temporary" test hooks.
Where to look
| Signal | Where to look |
|---|---|
| Feature type | Login handlers, API gateways, third-party SDK init, mobile app config, CI/CD scripts |
| Input entry | Password verification, header API-key checks, webhook signature validation |
| Static fields | const, private static final, module-level variables, .env.example with real values |
| Bypass branches | if (password.equals("...")) alongside bcrypt checks, debug flags in production paths |
| Crypto material | AES keys, HMAC secrets, JWT signing keys, PEM blocks committed to git |
| Connection strings | JDBC, MongoDB, Redis, SMTP URLs with embedded usernames and passwords |
| Client exposure | JavaScript bundles, mobile source, public config endpoints shipping server keys |
Appendix detail: Hardcoded secrets code reference.
Dangerous functions
Some language and platform APIs execute arbitrary code or commands when passed a string. eval() in JavaScript and Python, Function() constructors, script engines, and shell invocation with concatenated arguments are common examples. When attacker-controlled data reaches these functions, the impact is often full code execution in the application's process context.
Where to look
| Signal | Where to look |
|---|---|
| Feature type | Formula evaluators, rule engines, admin scripting, plugin loaders, template logic |
| Dynamic execution | eval, exec, compile, Function(, setTimeout with string arguments |
| Shell invocation | subprocess with shell=True, /bin/sh -c, Runtime.exec with concatenated commands |
| Reflection | Class.forName, importlib, user-controlled method or class names at runtime |
| Plugin loading | JARs, .so files, or scripts loaded from user-upload paths |
| Template logic | Jinja2 unsafe extensions, Velocity user templates, server-side scriptlets |
| Deserialization overlap | Native object deserialization treated as dynamic instantiation (see deserialization chapter) |
Appendix detail: Dangerous functions code reference.
Obsolete code
Dead code, test hooks, and outdated modules often linger after refactors. Some paths are still reachable through direct URLs, old API versions, or feature toggles left enabled. Obsolete authentication helpers, deprecated encryption routines, and temporary admin endpoints may lack the hardening applied to newer code. Attackers probe forgotten routes; insiders may know URLs that documentation no longer mentions.
Where to look
| Signal | Where to look |
|---|---|
| Feature type | Legacy login routes, old API versions (/v0/, /v1/), debug endpoints, profiling handlers |
| Test artifacts | Hardcoded users, mock payment flows, QA bypasses without environment gating |
| Feature flags | Toggles defaulting to on, experiments never removed, alternate upload paths |
| Deprecated handlers | @Deprecated controllers, duplicate login implementations, pre-refactor validation |
| Commented blocks | Disabled auth checks left in comments, large duplicated logic with weaker controls |
| Build gaps | Debug controllers in Release builds, test packages bundled into production JARs |
| Static analysis hits | Unreferenced methods, unreachable branches flagged by coverage or linters |
Appendix detail: Obsolete code code reference.
Framework secure defaults
Secure by default means the standard configuration is safe without extra steps. Web frameworks often provide CSRF middleware, template auto-escaping, secure session cookies, and parameterized data access. Teams can undermine these benefits with DEBUG=True, disabled CSRF, raw template modes, or secrets in source control.
Where to look
| Signal | Where to look |
|---|---|
| Config surfaces | settings.py, application.yml, appsettings.json, config/environments/*.rb, Express app.js |
| Debug in prod | DEBUG=True, app.debug, verbose stack traces, Werkzeug debugger exposed |
| CSRF disabled | @csrf_exempt, csrf().disable(), missing CSRF on cookie-auth forms |
| Raw output | \|safe, th:utext, @Html.Raw on user-controlled model fields |
| Cookie flags | Missing Secure, HttpOnly, SameSite on session identifiers |
| Hardcoded secrets | SECRET_KEY, JWT secrets, DB passwords committed in config files |
Appendix detail: Framework secure defaults code reference.
Client-side validation
Missing server-side validation is a business logic and input-trust flaw. Browsers can enforce pattern, required, maxlength, and JavaScript checks for user experience. Attackers bypass these controls with modified requests, custom HTTP clients, or browser devtools.
Where to look
| Signal | Where to look |
|---|---|
| Feature type | Checkout, registration, transfers, profile update, admin forms, SPA JSON APIs |
| Client-only guards | HTML5 pattern, min, max, required; React/Vue validators with no server mirror |
| Hidden/trusted fields | role, price, userId, discount in POST bodies accepted without server recomputation |
| API parity gaps | Mobile and third-party callers hit endpoints with weaker validation than the web UI |
| Missing server libs | Handlers with no Bean Validation, Pydantic, FluentValidation, or Go validator tags |
| Partial persistence | Invalid input rejected in UI but partially saved when API calls skip validation |
Appendix detail: Client-side validation code reference.
Insecure coding practice
These flaws come from convenience shortcuts, copy-pasted samples, or framework defaults left unchanged. The application still “uses HTTPS” or “uses JWT,” but the code does not actually validate the peer, token, or cookie the way the design assumes.
Where to look
| Pattern | Where to look | Red flags |
|---|---|---|
| TLS / HTTPS verification disabled | requests.get(..., verify=False), custom TrustManager that accepts all certs, NODE_TLS_REJECT_UNAUTHORIZED=0, gRPC/HTTP clients with insecure channel creds |
Comments like “fix cert later,” test code shipped to prod |
| JWT signature / key mishandling | get_unverified_claims, hardcoded HMAC secrets, accepting alg: none, JWKS without issuer/audience checks |
Auth middleware that trusts base64 payload segments |
| Insecure HTTP cookie flags | set_cookie without httponly/secure/samesite, legacy servlet cookies, framework session defaults |
Session ID in URL, year-long Max-Age, logout that only clears client cookie |
| Hardcoded secrets (related) | API keys, DB passwords, signing keys in source | See 4.9 § Hardcoded secrets |
| Weak or custom crypto (related) | MD5 passwords, home-grown AES, mixed hash/encrypt | See 4.8 Review Cryptography in Application Code § Cryptographic implementation, 4.8 Review Cryptography in Application Code § Non-standard crypto practices, 4.8 Review Cryptography in Application Code § Encryption and decryption mistakes |
| Dangerous dynamic execution (related) | eval, exec, script engines on user input |
See 4.9 § Dangerous functions |
| Sensitive data in logs or URLs (related) | Tokens/passwords in logs, credentials in GET | See 4.7 Review Information Disclosure and Logging § Sensitive data in URL, 4.7 Review Information Disclosure and Logging § Sensitive logging |
| Secrets in comments (related) | Password hints in HTML/JS comments | See 4.7 § Sensitive code comments |
| Insecure deserialization (related) | pickle.loads on untrusted bytes |
See 4.3 § Insecure deserialization |
| Framework defaults left weak (related) | DEBUG=True, permissive CORS, CSRF off | See 4.9 § Framework secure defaults |
Suggested additions for the same review pass: debug endpoints left enabled in production, permissive CORS with credentials, missing CSRF on state-changing cookie auth (4.5 Review Authentication, Session, and Access Control § CSRF), trust-all proxy headers without validation, and disabling security headers (CSP, HSTS) at the edge.
Appendix detail: Insecure coding practice code reference.
Worked Example (Hardcoded secrets)
We walk Hardcoded secrets in depth. Apply the same tracing steps to the other variants, adjusting sources and sinks from the tables above.
Sample vulnerable code (Python)
AWS_ACCESS_KEY = "AKIAIOSFODNN7EXAMPLE"
AWS_SECRET_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
@app.route("/admin/sync")
def admin_sync():
api_key = request.headers.get("X-API-Key")
# Hardcoded bypass key grants admin access without user authentication
if api_key == "nightly-etl-bypass-7k9m":
return admin_dashboard()
return "", 403
def sign_token(payload: dict) -> str:
# JWT signing key embedded in source — cannot rotate without redeploy
return jwt.encode(payload, "prod-jwt-hmac-static-v3", algorithm="HS256")
Step-by-step review walkthrough
- Search authentication checks. Look for
equals,==, orcompareagainst user-supplied passwords or API keys with string literals on either side. - Inspect constants and config modules. Read
config.py,.envfiles in git, andsettings.pyfor API keys, connection strings, and OAuth client secrets. - Review bypass branches. Flag OR conditions on password verification that accept a fixed string alongside normal hash checks.
- Trace encryption and signing. Follow JWT, HMAC, and field-encryption code for hardcoded keys, IVs, and salts instead of key-management services.
- Check client-side code. Mobile apps and front-end bundles must not ship server secrets; only public identifiers belong in client code.
- Follow third-party SDK initialization. Stripe, Twilio, and cloud SDK calls often hide inline keys in setup blocks.
- Confirm CI/CD injects secrets at runtime. Dockerfiles, Helm charts, and Terraform in git should reference secret names, not literal values.
Risk Impact (Family)
Authentication bypass. Hardcoded backdoor passwords or API keys let attackers skip normal credential checks entirely.
Credential theft and replay. Keys in source propagate to forks, backups, and decompiled binaries; stolen keys work until manual rotation.
Cloud and data exposure. Embedded AWS or database credentials may grant broad infrastructure access beyond the application tier.
Compliance and audit failure. Regulators and customers expect secrets in vaults with rotation logs; hardcoded values fail basic control reviews.
Incident response delay. When a key leaks, teams must redeploy code instead of rotating a secret in a manager within minutes.
Fix Principles
Primary safer patterns for the worked example follow. For other languages and variant-specific fixes, use the family appendix.
Python
Load secrets from environment or a secret backend. Remove test bypasses from production paths.
import os
import jwt
def get_secret(name: str) -> str:
value = os.environ.get(name)
if not value:
raise RuntimeError(f"Missing required secret: {name}")
return value
@app.route("/admin/sync")
def admin_sync():
expected = get_secret("ADMIN_SYNC_API_KEY")
if secrets.compare_digest(request.headers.get("X-API-Key", ""), expected):
return admin_dashboard()
return "", 403
def sign_token(payload: dict) -> str:
key = get_secret("JWT_SIGNING_KEY")
return jwt.encode(payload, key, algorithm="HS256")
Use pydantic-settings or django-environ for typed configuration. Run gitleaks or GitHub secret scanning in CI.
Verify During Review
Use the checklist below as a family-level pass over the variants above. Each item should map to evidence in the change under review.
- No passwords, API keys, tokens, or private keys appear as string literals in application code.
- Authentication logic has no hardcoded bypass branches alongside normal credential checks.
- Secrets load from environment variables, secret managers, or encrypted configuration at runtime.
- Front-end and mobile clients use public identifiers only; sensitive keys stay on the server.
- Secret scanning runs in CI and on historical commits when onboarding a repository.
- Rotation procedures exist for every secret class; hardcoded values cannot be rotated without redeploying code.
- User-controlled input does not reach
eval, equivalent dynamic execution, or unsafe deserialization APIs. - Shell commands use argument arrays and allowlists; no
shell=Trueorsh -cwith interpolated user data. - Plugin and template features use restricted DSLs or admin-only authoring with code review, not open user scripting.
- Dangerous functions in legacy modules are scheduled for removal or isolated from production routes.
- Static analysis and security tests cover known dangerous API usage in the codebase.
- Dead code, duplicate legacy endpoints, and test-only routes are removed or strictly environment-gated.
- Feature flags that expose alternate code paths have owners, expiry dates, and production defaults that fail secure.
- Deprecated API versions receive the same authentication, authorization, and input validation as current versions—or are shut down.
- Production builds exclude debug, profiling, and administrative utilities not required in prod.
- Coverage or static analysis confirms unreachable security-sensitive code is deleted, not commented out.
- Technical debt tickets for obsolete security paths are prioritized alongside new feature work.
- Framework secure defaults are enabled and not overridden in production configuration.
- Debug, verbose errors, and permissive CORS are confined to local development.
- Session and CSRF protections match the authentication model (cookie sessions need CSRF).
- Template and API output paths use framework escaping unless a documented exception exists.
- Dependencies are current and monitored; configuration drift is reviewed in each release.
- Every user-editable field has equivalent server-side validation before business logic runs.
- HTML5, JavaScript, and mobile validations are treated as UX only, not security controls.
- Trusted values (price, role, user ID, discount eligibility) are computed server-side, not read from the client.
- Invalid input returns consistent 400 responses with safe error messages; handlers do not partially persist bad data.
- API endpoints used by SPAs and mobile apps enforce the same rules as server-rendered forms.
- Security tests bypass the front end and send out-of-range, missing, and malformed fields to each endpoint.
- No production code path uses
verify=False, trust-all TLS callbacks, or equivalent. - JWT validation enforces signature, allowed algorithms,
exp, andiss/audwhere applicable; noverify_signature=Falsein deployed branches. - Signing keys load from secret stores or JWKS with rotation; no long-lived hardcoded symmetric secrets in source.
- Session cookies use HttpOnly + Secure + deliberate SameSite; lifetimes match policy.
- User-controlled URLs are not fetched with TLS verification disabled (pair with SSRF allowlists).
- Related chapters (hardcoded secrets, CSRF, session management) are checked when any finding above is present.
Code Reference (Appendix)
Payloads, language-specific sinks, multi-language examples, and full fix catalogs for every variant live in 4.9 code reference — Review Secrets, Defaults, and Dangerous APIs.
Reference
-
Appendix — 4.9 code reference
- CWE-321: Use of Hard-coded Cryptographic Key
- OWASP Secrets Management Cheat Sheet
- NIST SP 800-57: Recommendation for Key Management
- AWS Secrets Manager documentation
- HashiCorp Vault documentation
- Python os.environ
- pydantic-settings documentation
- Spring Cloud Config
- Azure Key Vault configuration provider
- Go crypto/subtle ConstantTimeCompare
- CWE-95: Improper Neutralization of Directives in Dynamically Evaluated Code
- CWE-78: OS Command Injection
- OWASP Code Injection
- Python ast.literal_eval
- Python subprocess security considerations
- Java ProcessBuilder
- System.Text.Json documentation
- Go exec.Command
- CWE-561: Dead Code
- CWE-489: Active Debug Code
- OWASP Web Security Testing Guide — Configuration and Deployment Management
- Go build constraints
- Python vulture
- ArchUnit user guide
- staticcheck documentation
- OWASP — Secure Configuration Cheat Sheet
- Django — Deployment checklist
- Django — Settings reference
- Flask — Configuration handling
- Spring Security — Getting started
- ASP.NET Core — Security overview
- Gin — Mode and middleware
- CWE-602: Client-Side Enforcement of Server-Side Security
- CWE-20: Improper Input Validation
- OWASP Input Validation Cheat Sheet
- Pydantic v2 — field constraints
- Marshmallow documentation
- Jakarta Bean Validation
- Hibernate Validator
- ASP.NET Core model validation
- FluentValidation
- go-playground/validator
- OWASP Transport Layer Protection Cheat Sheet
- Python requests — SSL Cert Verification
- Python PyJWT — Usage (decode with verification)
- RFC 7519 — JSON Web Token (JWT)
- OWASP JSON Web Token Cheat Sheet for Java
- OWASP Session Management Cheat Sheet
- MDN — Set-Cookie (HttpOnly, Secure, SameSite)
- Flask — Set-Cookie parameters
- Microsoft Learn — HttpClient certificate validation
- Microsoft Learn — TokenValidationParameters
- Java HttpsURLConnection documentation
- jjwt — JJWT README (signature verification)
- Go crypto/tls — Config
- Go golang-jwt — jwt.Parse
- Go net/http — Cookie fields