Skip to content

Security Standards

Severity Model: Rules are tagged P0 (Critical), P1 (High), or P2 (Medium). P0 and P1 findings must block merge. P2 findings should be flagged as warnings.

This framework is inspired by Omar Gate’s multi-layer security analysis model.

  • Use parameterized queries exclusively. Never construct SQL via string concatenation or interpolation with user input.
  • ORMs and query builders are preferred. Raw SQL must use bind parameters.
# BAD — P0 violation
query = f"SELECT * FROM users WHERE id = {user_input}"
cursor.execute(query)
# GOOD
cursor.execute("SELECT * FROM users WHERE id = %s", (user_input,))
# BAD — P0 violation
User.where("name = '#{params[:name]}'")
# GOOD
User.where(name: params[:name])
// BAD — P0 violation
const query = `SELECT * FROM users WHERE id = ${userId}`;
// GOOD
const result = await db.query("SELECT * FROM users WHERE id = $1", [userId]);
  • Never pass unsanitized user input to shell commands, eval(), exec(), or system().
  • Use language-specific safe APIs instead of shell invocation.
  • If shell execution is unavoidable, use allowlists for permitted commands and arguments.
# BAD — P0 violation
os.system(f"convert {user_filename} output.png")
subprocess.call(user_input, shell=True)
# GOOD
subprocess.run(["convert", validated_filename, "output.png"], shell=False)
// BAD — P0 violation
eval(userInput);
require("child_process").exec(userCommand);
// GOOD
require("child_process").execFile("convert", [validatedFilename, "output.png"]);
  • Encode all output rendered in HTML contexts. Use framework-provided escaping by default.
  • Never use innerHTML, dangerouslySetInnerHTML, or v-html with untrusted data.
  • Configure Content Security Policy (CSP) headers to restrict inline scripts.
// BAD — P1 violation
element.innerHTML = userInput;
// GOOD
element.textContent = userInput;
// BAD — P1 violation
<div dangerouslySetInnerHTML={{ __html: userInput }} />
// GOOD
<div>{userInput}</div>
  • Validate and sanitize all URL inputs. Allowlist permitted domains and protocols.
  • Block requests to internal/private IP ranges (10.x, 172.16-31.x, 192.168.x, 127.x, 169.254.x).
  • Do not allow user input to control the full URL of outbound HTTP requests.
  • Never pass user input directly into template expressions or template strings that are evaluated server-side.
  • Use sandboxed template engines. Separate data from template logic.
  • Never implement custom authentication logic when framework/library solutions exist.
  • Verify authentication on every request — do not rely on client-side checks alone.
  • Token validation must check expiration, signature, and issuer.
  • Never compare secrets or tokens with == — use constant-time comparison functions.
# BAD — P0 violation (timing attack)
if provided_token == stored_token:
grant_access()
# GOOD
import hmac
if hmac.compare_digest(provided_token, stored_token):
grant_access()
  • Every endpoint must enforce authorization. Verify the authenticated user has permission for the specific resource and action.
  • Never rely solely on hiding UI elements for access control.
  • Use RBAC or policy-based authorization. Check permissions server-side on every request.
  • Enable CSRF protection on all state-changing endpoints (POST, PUT, PATCH, DELETE).
  • Use framework-provided CSRF mechanisms (Rails protect_from_forgery, Django CSRF middleware, etc.).
  • API endpoints using token-based auth (Bearer tokens) are exempt but must validate the token on every request.
  • Set secure cookie attributes: Secure, HttpOnly, SameSite=Strict (or Lax).
  • Regenerate session IDs after authentication.
  • Implement session expiration and idle timeouts.
  • Never commit secrets, API keys, passwords, tokens, or credentials to source code.
  • Use environment variables, secret management services (AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager), or framework credential systems (Rails credentials).
  • Scan for secrets in CI with tools like git-secrets, truffleHog, or detect-secrets.

Common patterns to detect and reject:

# BAD — P0 violations (any of these patterns in source code)
API_KEY = "sk-abc123..."
password = "hardcoded_password"
aws_secret_access_key = "AKIA..."
DATABASE_URL = "postgres://user:pass@host/db"
private_key = "-----BEGIN RSA PRIVATE KEY-----"
  • Never commit .env files to version control. Add .env to .gitignore.
  • Provide .env.example with placeholder values (never real credentials).
  • In production, use secret management services — not .env files.
  • Rotate credentials on a regular schedule.
  • Implement credential rotation without downtime.
  • Log credential access for audit purposes.

Never use these functions with untrusted input. If used with trusted input, add a comment explaining why.

Language Banned Functions
Python eval(), exec(), pickle.loads() (untrusted), yaml.load() (use safe_load), os.system(), subprocess.call(..., shell=True)
JavaScript/TypeScript eval(), Function(), setTimeout(string), setInterval(string), document.write()
Ruby eval(), send() with user input, system() with interpolation, Marshal.load() (untrusted), YAML.load() (use YAML.safe_load)
Java/Kotlin Runtime.exec() with string, ObjectInputStream.readObject() (untrusted), ScriptEngine.eval()
Rust std::process::Command with unsanitized input
Swift NSExpression with user input, Process with unsanitized args
  • Never deserialize untrusted data using native serialization formats (Python pickle, Java ObjectInputStream, Ruby Marshal, PHP unserialize).
  • Use safe data formats (JSON, Protocol Buffers) for untrusted data exchange.
  • If native deserialization is required, validate and sanitize before deserializing.
  • Use cryptographically secure random number generators for security contexts (tokens, keys, session IDs, nonces).
Language Insecure (avoid for security) Secure (use for security)
Python random.random() secrets.token_urlsafe(), secrets.token_hex()
JavaScript Math.random() crypto.randomUUID(), crypto.getRandomValues()
Ruby rand() SecureRandom.hex(), SecureRandom.uuid()
Java/Kotlin java.util.Random java.security.SecureRandom
Rust rand::rngs::OsRng (already secure by default)
  • Run dependency vulnerability scanning in CI/CD. Block merges on critical/high CVEs.
Language Tool
Python pip-audit, safety
JavaScript/TypeScript pnpm audit, npm audit, yarn audit
Ruby bundler-audit (bundle-audit command)
Java/Kotlin OWASP Dependency-Check, gradle dependencyCheckAnalyze
Rust cargo audit, cargo deny
Swift — (use GitHub Dependabot)
Dart — (use GitHub Dependabot)
Zig — (manual review)
  • Always commit lock files (package-lock.json, Gemfile.lock, Cargo.lock, poetry.lock, etc.).
  • CI must install from lock files (npm ci, BUNDLE_FROZEN=1 bundle install, etc.).
  • Never use HTTP (non-HTTPS) URLs for package registries or dependency sources.
  • All dependency sources must use HTTPS.
  • Pin major and minor versions in production dependencies.
  • Review and test dependency updates before merging.
  • Never adopt a 3rd-party dependency version published less than 72 hours (3 days) ago. This mitigates supply-chain attacks where malicious code is injected into a new release and detected/reverted within the first few days.
  • Applies to all languages and package ecosystems — PyPI, npm, crates.io, RubyGems, Maven Central, pub.dev, Swift packages, and Zig packages.
  • CI enforcement: Validate the publish date of every added or upgraded dependency against its registry. Block merges when any dependency version is younger than 3 days.
  • Exception process: Emergency security patches (e.g., a critical CVE fix) may bypass the age gate with:
    1. Explicit approval from a team lead or security owner.
    2. A documented justification in the PR description.
    3. A follow-up review within 24 hours of the bypass.
Language Registry / Publish Date Source
Python PyPI JSON API (upload_time field)
JavaScript/TypeScript npm registry API (time field)
Ruby RubyGems versions API (created_at field)
Java/Kotlin Maven Central (lastModified / Sonatype API)
Rust crates.io API (created_at field)
Swift GitHub release / tag date
Dart pub.dev API (published field)
Zig Manual review of upstream commit/tag date
  • Never ship or deploy with default credentials. All default passwords, API keys, and admin accounts must be changed before deployment.
  • Fail loudly if default credentials are detected at startup.
  • Enforce HTTPS for all external communication. Redirect HTTP to HTTPS.
  • Use TLS 1.2+ for all connections. Disable older TLS/SSL versions.
  • Configure security headers on all HTTP responses:
Header Value Purpose
Content-Security-Policy Restrict sources Prevents XSS
Strict-Transport-Security max-age=31536000; includeSubDomains Forces HTTPS
X-Content-Type-Options nosniff Prevents MIME sniffing
X-Frame-Options DENY or SAMEORIGIN Prevents clickjacking
Referrer-Policy strict-origin-when-cross-origin Controls referrer leakage
  • Never enable debug mode or verbose error messages in production.
  • Log detailed errors server-side; return generic error messages to clients.
  • Ensure framework debug flags are off: DEBUG=False (Django), config.consider_all_requests_local = false (Rails), NODE_ENV=production.
  • Never log passwords, tokens, API keys, credit card numbers, or PII.
  • Use structured logging with field-level redaction.
  • Review log output for accidental sensitive data exposure.
# BAD — P1 violation
logger.info(f"User login: {username}, password: {password}")
# GOOD
logger.info("User login", extra={"username": username})
  • Minimize PII collection — only collect what is necessary.
  • Encrypt PII at rest and in transit.
  • Implement data retention policies and deletion capabilities.
  • Use established encryption libraries — never implement custom cryptography.
  • Encrypt sensitive data at rest (database-level or application-level encryption).
  • All data in transit must use TLS.
Language Tool Usage
Python bandit bandit -r src/
JavaScript/TypeScript eslint-plugin-security Add to ESLint config
Ruby brakeman brakeman --no-pager
Java/Kotlin SpotBugs + Find Security Bugs Gradle/Maven plugin
Rust cargo-geiger (unsafe audit), clippy cargo geiger, cargo clippy --all-targets --all-features -- -D warnings
Swift Use Xcode static analyzer
Dart dart analyze Built-in security rules
Tool Scope
git-secrets Pre-commit hook
trufflehog Full repo/PR scan in CI (recommended, see below)
detect-secrets Pre-commit + CI
GitHub Secret Scanning Automatic (GitHub repos)

TruffleHog is the recommended CI secret scanner. Its GitHub Action is fully free (Gitleaks’ action requires a license for v2+), it is actively maintained, and unignored findings block merge per the P0 hardcoded-credentials rule above.

  • Keep verification OFF by default. Do not pass --only-verified. Verification sends detected secrets to third-party endpoints (a security concern in itself), needs outbound network many CI environments restrict, and adds latency/flakiness. An expired/rotated secret is still a finding worth removing from history.
  • Suppress false positives at the finest granularity that works. Prefer an inline // trufflehog:ignore comment on the exact offending line so the rest of the file is still scanned. Reserve a committed .trufflehog-ignore path-exclude file (consumed via --exclude-paths, one path regex per line) for paths that are inherently all-noise. Never exclude a whole tree just to silence one finding — that hides future real secrets.
  • Common all-noise sources: lockfiles and *.checksums (SHA/MD5 hashes trip generic detectors), minified/vendored dist/ bundles, test fixtures and example/placeholder tokens, and binary assets that base64-decode to non-secret data (fonts, image data URIs). Hex color codes and UUID constants are better handled with an inline trufflehog:ignore where they appear.

Ship-with-standards templates: templates/.trufflehog-ignore (allowlist with format docs) and templates/trufflehog.yml.example (free action, verification off, merge-blocking). setup.sh installs the allowlist into consumer projects; add the workflow with setup.sh --workflow. Noisy, ignored scanner output is why teams disable scanning entirely — the allowlist keeps signal high so the P0 rule stays enforced.