Secure Source Code Review Checklist

The white-box code-review checklist turned into a how-to-review field guide: trace untrusted source to dangerous sink for auth, authorization, sessions, injection, XSS, crypto, secrets, error handling, file handling, business logic, dependencies, config and memory safety — plus per-language grep patterns (Java, Python, JS/Node, PHP, C#, Go, Ruby) — each with what to grep, the scenario, the finding, and the fix.

LazyHackers.in — Checklist

🔎 Secure Source Code Review Checklist

Source → sink, item by item: what to grep · the scenario · the finding · the fix

☰   How to use this guide

Secure code review is one discipline: follow the data. A SAST tool flags candidates; a finding is real only when you trace an untrusted source (HTTP param, header, body, file, DB, queue, IPC, env) to a dangerous sink (exec, query, eval, deserialize, file op, redirect) with no effective sanitisation between them. This guide turns every checklist line into what-to-grep + how-to-confirm. Pair with the dynamic checklists (web/API/mobile) to prove exploitability.

Use Semgrep/CodeQL for breadth, then verify by hand — triage false positives. Each section ends with a coverage table mapping every line to a grep/Semgrep hint and a report-ready finding.
Prioritise injection sinks, auth/authz gaps, deserialization and hardcoded secrets — that's where high-severity bugs concentrate. Confirm exploitability or explicitly note ‘needs dynamic confirmation’; never paste raw scanner output as findings.
# Breadth pass, then trace by hand
semgrep --config p/owasp-top-ten --config p/secrets .
# Map sources/sinks quickly with ripgrep
rg -n --no-heading -e 'request\.|req\.(query|body|params)|\$_(GET|POST|REQUEST)|getParameter' .   # sources
rg -n --no-heading -e 'exec|system|eval|query\(|innerHTML|deserialize|readObject|unserialize' .   # sinks

0   Prep & approach

Get the whole codebase and config, understand the architecture and trust boundaries, and map sources, sinks and the enforcement points for auth/authz before diving in.

⚑ Report as: “Review baseline: sources, sinks and auth/authz enforcement points mapped”
🛡 Fix: Threat-model the high-value flows first (auth, payment, admin, upload); decide on data-flow tracing + targeted grep + framework-specific review; treat SAST as a starting point, not the answer.

Prep & approach — full coverage

Checklist itemHow to reviewOutcome
Full source + build + config + manifestsobtain repoComplete codebase
Architecture/frameworks/trust boundariesread docs/codeArchitecture understood
Map sources (untrusted input)rg for request inputsSources mapped
Map sinks (dangerous functions)rg for sinksSinks mapped
Identify auth/authz enforcement pointslocate middleware/guardsEnforcement points mapped
Threat-model high-value flowsprioritise flowsHigh-value flows identified
Choose approach (trace + grep + framework)plan reviewReview approach set
SAST baseline (verify manually)semgrep/codeqlSAST baseline

1   Authentication (code)

Look for missing checks, plaintext/non-constant-time compares, weak hashing, hardcoded creds, and logic that grants access without verifying the password.

rg -n -e 'md5|sha1\(' -e 'password\s*==' -e '==\s*password' -e 'hardcoded|backdoor'
rg -n -e 'if\s*\(\s*user\s*!=\s*null' -e 'verify.*=.*true'      # auth-bypass logic smells
# JWT: secret + verification
rg -n -e 'jwt|alg.*none|verify\(|decode\('
⚑ Report as: “Weak password hashing (MD5/SHA1/unsalted) / auth bypass via logic flaw”
🛡 Fix: Hash with bcrypt/argon2/scrypt; constant-time compares (hash_equals); no hardcoded creds/backdoors; verify JWTs (pin alg, reject none, no hardcoded secret); CSPRNG for tokens; rate-limit auth at the code/middleware level.

Authentication (code) — full coverage

Checklist itemWhat to grep / reviewReport as
Auth check missing on protected routeaudit route guardsMissing authentication
Auth client-side / trusted client claimtrace claim usageClient-side authentication
Plaintext / non-constant-time comparerg password==Insecure credential compare
Weak password hashingrg md5/sha1Weak password hashing
Hardcoded credentials / backdoorrg secretsHardcoded credential
JWT sig not verified / none / hardcodedrg jwtJWT misuse
Token via non-CSPRNGrg Random/randInsecure token generation
Reset token predictable/non-expiring/reusablereview reset flowWeak reset token
OTP logic flawsreview OTP codeOTP logic flaw
Auth bypass via logic flawreview auth branchesAuthentication bypass
No rate limiting at code levelcheck middlewareMissing rate limiting

2   Authorization (code)

The big one: ownership and role checks. Find queries using a user-supplied ID without an ownership filter, missing role guards, and mass assignment.

# Object access without ownership filter (IDOR/BOLA in code)
rg -n -e 'findById|getById|where.*id|params\.id|req\.params'      # then check ownership filter exists
# Missing role guards / mass assignment
rg -n -e '@PreAuthorize|@Authorize|hasRole|isAdmin'              # presence/absence on sensitive fns
rg -n -e 'bind\(|@ModelAttribute|mass.?assign|update\(req\.body|create\(req\.body'
⚑ Report as: “Object ownership not checked before access (IDOR/BOLA in code) / mass assignment”
🛡 Fix: Add server-side ownership checks on every object access; role/permission guards on every sensitive function (default-deny); never bind whole request bodies to models (whitelist fields); don't trust client-supplied role/id; enforce authz at the entry point, not only the UI.

Authorization (code) — full coverage

Checklist itemWhat to grep / reviewReport as
Ownership not checked (IDOR/BOLA)trace ID→query, no filterMissing object authorisation
Role/permission check missing (BFLA)audit sensitive fnsMissing function authorisation
AuthZ on hidden field / client roletrace role sourceClient-controlled authorisation
Mass assignment binds all fieldsrg bind/ModelAttributeMass assignment
User ID in query w/o ownership filterreview queriesInsecure direct object query
Privileged action no re-checkaudit privileged pathsMissing privilege re-check
AuthZ in UI but bypassable (API/job)check alternate entriesAuthorisation bypass via alt entry

3   Session management (code)

Session rotation, invalidation, cookie flags and identifier entropy — in the framework's session config and login/logout handlers.

⚑ Report as: “Session not rotated on login (fixation) / cookie flags not set”
🛡 Fix: Rotate the session on login/privilege change; invalidate server-side on logout and password change; set HttpOnly+Secure+SameSite; keep tokens out of localStorage; use the framework's CSPRNG session IDs; set a sane timeout.

Session management (code) — full coverage

Checklist itemWhat to grep / reviewReport as
No rotation after loginreview login handlerSession fixation
Not invalidated on logout/pw changereview logout/pw handlersSession invalidation missing
Cookie flags not setrg cookie configInsecure cookie flags
Token in localStorage (frontend)rg localStorageToken in localStorage
Excessive/no timeoutreview session configExcessive session lifetime
Predictable session identifierreview ID generationPredictable session ID

4   Injection (source → sink)

Trace each source to each sink. String-built SQL, command exec, eval, deserialize, template render, and XML parsing of untrusted data are the high-severity hits.

# SQL string concat (vs parameterized)
rg -n -e 'SELECT .*\+|format.*SELECT|f".*SELECT|query\(.*\$\{|\.raw\('
# Command / code execution on input
rg -n -e 'exec\(|system\(|popen|Runtime\.exec|child_process|shell=True|eval\(|Function\('
# Deserialization
rg -n -e 'readObject|unserialize|pickle\.loads|yaml\.load\b|Marshal\.load|BinaryFormatter'
# XXE: parser without hardening
rg -n -e 'DocumentBuilderFactory|SAXParser|XmlReader|libxml|etree'
⚑ Report as: “SQL injection via string-concatenated query / insecure deserialization of untrusted data”
🛡 Fix: Parameterise all queries (prepared statements / bound params / safe ORM); never pass untrusted input to exec/eval; avoid deserializing untrusted data (use safe formats / allowlists); disable XML external entities; validate redirect/URL targets; bound regex.

Injection — full coverage

Checklist itemWhat to grep / reviewReport as
String-concatenated SQLrg SELECT+inputSQL injection
Dynamic query from request datatrace query buildDynamic SQL
ORM raw query w/ untrusted inputrg .raw(ORM raw injection
NoSQL query from user objectreview NoSQL buildNoSQL injection
Stored proc with concat inputreview proc callsStored-proc injection
OS command with user inputrg exec/systemOS command injection
eval / dynamic code on inputrg eval/FunctionCode injection
Template render of user inputreview template usageServer-side template injection
Insecure deserializationrg readObject/pickleInsecure deserialization
Reflection / dynamic class loadrg reflectionUnsafe reflection
LDAP/XPath/XML from inputreview query buildLDAP/XPath injection
Header / CRLF injectionrg header set + inputCRLF injection
Open redirectrg redirect+inputOpen redirect
SSRF (user URL to HTTP client)trace URL→clientSSRF
XXE (external entities enabled)rg XML parserXXE
Regex from user input (ReDoS)rg new RegExp+inputReDoS
Log injectionrg log + raw inputLog injection

5   Output encoding / XSS (code)

Find user data reaching a browser sink without encoding, and disabled auto-escaping.

rg -n -e 'innerHTML|dangerouslySetInnerHTML|v-html|document\.write'
rg -n -e '\|\s*safe|\{\{\{|Html\.Raw|mark_safe|autoescape\s*(off|false)'
⚑ Report as: “User input rendered without output encoding (XSS) / auto-escaping disabled”
🛡 Fix: Context-aware output encoding; keep framework auto-escaping on (avoid |safe / {{{ }}} / Html.Raw with user data); avoid innerHTML/dangerouslySetInnerHTML with user input; set Content-Type + X-Content-Type-Options; guard CSV/Excel exports against formula injection.

Output encoding / XSS — full coverage

Checklist itemWhat to grep / reviewReport as
User input rendered unencodedtrace renderCross-site scripting
innerHTML / dangerouslySetInnerHTML / v-htmlrg sinksDOM XSS sink
Auto-escaping disabledrg |safe/Html.RawDisabled auto-escaping
Input in JS context / inline handlerreview JS contextXSS in JS context
Content-Type not set / sniffablecheck headersContent sniffing risk
CSV/Excel export no formula guardreview exportCSV formula injection

6   Cryptography (code)

Weak algorithms, ECB, static keys/IVs, non-CSPRNG and trust-all TLS — grep the crypto API usage.

rg -n -e 'DES|RC4|MD5|SHA1|ECB|Math\.random|new Random\(|InsecureSkipVerify|verify=False|trustAllCerts'
⚑ Report as: “Weak/insecure cryptography (static key / ECB / non-CSPRNG / trust-all TLS)”
🛡 Fix: Strong algorithms + modes (AES-GCM); random per-message IVs; keys from a vault/KMS, never in repo/alongside ciphertext; CSPRNG for security values; proper cert/hostname validation; encrypt sensitive data before storage.

Cryptography (code) — full coverage

Checklist itemWhat to grep / reviewReport as
Weak algorithmrg DES/RC4/MD5/SHA1Weak cryptographic algorithm
ECB moderg ECBECB mode
Hardcoded key/IV/saltrg key constantsHardcoded crypto material
Static/predictable IVrg IVStatic IV
Non-CSPRNG for tokensrg Math.random/randInsecure randomness
Home-grown cryptoreview custom cryptoCustom cryptography
Trust-all cert/hostname validationrg InsecureSkipVerifyImproper cert validation
Sensitive data not encrypted at restreview storageUnencrypted sensitive data
Key stored with ciphertext / in reporeview key locationKey co-located with data

7   Secrets management (code)

Hardcoded keys, secrets in config/comments/history. Scan the working tree and the full git history.

trufflehog git file://. ; gitleaks detect --source .
rg -n -e 'api[_-]?key|secret|password|BEGIN (RSA|PRIVATE) KEY|AKIA[0-9A-Z]{16}'
git log -p -S 'password' | head            # secrets that were committed then removed
⚑ Report as: “Hardcoded secret / private key committed to repository (incl. history)”
🛡 Fix: Move secrets to a vault / env injected at runtime; never commit secrets/keys/.env; rotate anything that was committed (history is forever); scrub history if needed; pre-commit secret scanning; don't log/print secrets.

Secrets management (code) — full coverage

Checklist itemWhat to grep / reviewReport as
Hardcoded keys/passwords/tokenstrufflehog/rgHardcoded secret
Secrets in committed configreview config filesSecret in config
Secrets in source commentsrg commentsSecret in comment
Private keys / certs in reporg BEGIN KEYPrivate key in repo
Cloud credentials hardcodedrg AKIA/cloud keysCloud credential in code
Connection strings with credsrg connection stringEmbedded DB credential
.env / secrets in VCS historygitleaks / git log -SSecret in git history
Secrets logged/printedrg log(secret)Secret in logs

8   Error handling & logging (code)

Verbose errors to users, sensitive data in logs, swallowed exceptions and fail-open paths.

⚑ Report as: “Verbose error/stack trace returned to user / fail-open access logic”
🛡 Fix: Return generic errors to users, log details server-side; never log PII/secrets/tokens; don't swallow security-relevant exceptions; fail closed on error; disable debug in prod; audit-log security-relevant actions.

Error handling & logging — full coverage

Checklist itemWhat to grep / reviewReport as
Verbose errors/stack traces to userreview error handlersVerbose error disclosure
PII/secrets/tokens in logsrg log + sensitiveSensitive data in logs
Exceptions swallowed silentlyrg empty catchSwallowed exception
Fail-open logic on errorreview error pathFail-open access
Debug mode/endpoints in prod pathrg debug=trueDebug in production
No audit logging on security actionscheck audit coverageMissing audit logging

9   File handling (code)

Path from user input without canonicalisation, unrestricted uploads, and unsafe archive extraction.

rg -n -e 'new File\(|open\(|fopen|readFile|sendFile|include\(|require\(' # + check input canonicalization
rg -n -e 'getOriginalFilename|move_uploaded_file|MultipartFile|extractall|ZipEntry'
⚑ Report as: “Path traversal via uncanonicalised user input / unrestricted file upload”
🛡 Fix: Canonicalise and validate paths against a base dir; validate upload type by content; store outside web root, random names; check archive entry paths (zip-slip); secure temp-file creation; size limits.

File handling (code) — full coverage

Checklist itemWhat to grep / reviewReport as
Path from input w/o canonicalizationtrace path buildPath traversal
Upload no type/extension/content checkreview upload handlerUnrestricted upload
Upload to web-accessible/executable pathreview storage pathExecutable upload location
User filename used directlyrg getOriginalFilenameUnsafe filename
Archive extraction w/o path checksrg extractall/ZipEntryZip slip
Temp file race / insecure tempreview temp creationInsecure temp file
No file-size limitcheck size limitMissing size limit

10   Business logic (code)

Server-side re-validation, atomicity on financial ops, race conditions, and trusting client-supplied price/amount.

⚑ Report as: “Client-supplied price/amount trusted / missing atomicity on financial operation”
🛡 Fix: Re-validate everything server-side; use transactions/locks/idempotency on financial and one-time operations; handle negative/overflow values; enforce workflow state; never trust client price/quantity.

Business logic (code) — full coverage

Checklist itemWhat to grep / reviewReport as
Client-side validation onlycheck server re-validationMissing server-side validation
Race condition (TOCTOU)review check-then-actRace condition
Missing atomicity on financial opreview transactionsNon-atomic financial operation
Negative/overflow not handledreview numeric handlingNumeric validation flaw
Workflow state not enforcedreview state machineWorkflow bypass
Replay not preventedcheck nonce/idempotencyMissing replay protection
Trusting client price/amount/qtytrace amount sourceClient-controlled value trusted

11   Dependencies / supply chain

Known-vuln versions, untrusted sources, typosquats and build scripts running untrusted code.

npm audit ; pip-audit ; osv-scanner -r . ; trivy fs .   # depending on stack
⚑ Report as: “Vulnerable dependency version with known CVE in manifest”
🛡 Fix: Update vulnerable deps; pin versions with lockfiles + integrity hashes; vet sources and watch for typosquats; remove unused deps; surface transitive vulns; sandbox/untrust build scripts.

Dependencies / supply chain — full coverage

Checklist itemWhat to grep / reviewReport as
Vulnerable versions (CVE)npm/pip-audit, osv-scannerVulnerable dependency
Outdated / unmaintained libsreview manifest datesOutdated dependency
Untrusted source / no integritycheck lockfile/hashesMissing dependency integrity
Typosquatted packagereview namesTyposquatted dependency
Excessive/unused depsdepcheckExcessive dependencies
Transitive vuln not surfacedosv-scanner -rTransitive vulnerability
Build script runs untrusted codereview build scriptsUnsafe build script

12   Configuration & hardening

Insecure framework defaults, CORS, missing headers/CSRF, disabled TLS verification, permissive permissions.

rg -n -e 'cors|Access-Control-Allow-Origin|debug\s*=\s*true|csrf.*disable|verify\s*=\s*False|chmod 777'
⚑ Report as: “CORS misconfiguration in code / CSRF protection disabled on state-changing routes”
🛡 Fix: Harden framework defaults (no debug/default keys); CORS allowlist (no * with credentials/reflection); set security headers in middleware; CSRF protection on all state-changing routes; never disable TLS verification; least-privilege file permissions; no env-specific creds baked in.

Configuration & hardening — full coverage

Checklist itemWhat to grep / reviewReport as
Insecure framework defaultsreview configInsecure default configuration
CORS misconfig (* + creds / reflected)rg corsCORS misconfiguration
Missing security headersreview middlewareMissing security headers
CSRF disabled / not appliedrg csrfCSRF protection gap
TLS verification disabledrg verify=FalseDisabled TLS verification
Overly permissive file/dir permsrg chmod 777Permissive permissions
Hardcoded env-specific valuesreview configEnvironment value hardcoded

13   Memory safety (C / C++ / unmanaged)

C/C++/unmanaged only: unbounded copies, missing bounds checks, integer overflow, UAF and format strings.

rg -n -e '\bstrcpy\b|\bstrcat\b|\bsprintf\b|\bgets\b|\bmemcpy\b'
rg -n -e 'printf\s*\(\s*[a-zA-Z_]'      # format string: printf(user_input)
⚑ Report as: “Unbounded copy (strcpy/sprintf/gets) leading to buffer overflow”
🛡 Fix: Use bounded functions (strncpy/snprintf, or safer string types); validate all buffer indices; check for integer overflow before allocation; avoid UAF/double-free (RAII/smart pointers); never pass user input as a format string; check malloc/realloc returns.

Memory safety (C/C++) — full coverage

Checklist itemWhat to grep / reviewReport as
Unbounded copy (strcpy/sprintf/gets)rg unsafe funcsBuffer overflow
Missing bounds checksreview index useOut-of-bounds access
Integer overflow → undersized allocreview size mathInteger overflow
Use-after-free / double-freereview lifetimesUse-after-free
Format string vulnrg printf(var)Format string vulnerability
Off-by-one in loop/bufferreview loop boundsOff-by-one
Unchecked malloc/reallocreview return checksUnchecked allocation
Language-specific gotchas (what to grep)

L   Language-specific gotchas

Match these to the stack — the dangerous sinks and framework footguns per language.

What to grep, by language

LanguageDangerous sinks & footguns to grep
Java/JVMRuntime.exec, ProcessBuilder, ObjectInputStream.readObject, Jackson/XStream gadgets, Statement+concat, DocumentBuilderFactory/SAXParser (XXE), SpEL/OGNL, missing @PreAuthorize, @ModelAttribute binding, trust-all TrustManager
Pythoneval/exec/pickle.loads, subprocess(..., shell=True), os.system, string-formatted SQL, yaml.load (non-safe), Jinja2 |safe/autoescape off, flask debug=True
JavaScript/Node/TSeval/Function()/child_process.exec, innerHTML/dangerouslySetInnerHTML, prototype pollution (__proto__ merge), SQL string templates, vm misuse, insecure JWT lib usage, regex from input (ReDoS)
PHPeval/system/exec/shell_exec/passthru, include/require with input (LFI/RFI), unserialize on untrusted data, SQL concat (vs PDO prepared), extract() on request, loose == on auth tokens (type juggling)
C#/.NETProcess.Start with input, string-concat SQL, BinaryFormatter, Html.Raw/disabled request validation, XmlReader/XmlDocument with DTD (XXE), missing [Authorize]
Goexec.Command via shell, fmt.Sprintf into SQL, text/template vs html/template, InsecureSkipVerify: true, path join without filepath.Clean
Rubyeval/system/backticks/%x, Marshal.load/unsafe YAML, send/public_send with user method, mass assignment without strong params, string-interpolated SQL
Category-specific review

C   Category-specific review

Tailor the review to the component type — the same source→sink discipline, focused on what matters per layer.

By component — full coverage

Component / itemWhat to grep / reviewReport as
Web backend: route authz coverageaudit every sensitive routeMissing route authorisation
Web backend: CSRF on state-changing routescheck CSRF middlewareCSRF gap
Web backend: mass assignment / strong paramsreview bindingMass assignment
Web backend: template auto-escapingcheck escapingDisabled auto-escaping
Web backend: session/cookie configreview configInsecure session config
API: authz per endpoint (BOLA/BFLA)audit endpointsMissing API authorisation
API: excessive data in serializerreview response modelsExcessive data exposure
API: input validation/schemacheck schemasMissing input validation
API: rate limiting at code/middlewarecheck throttlingMissing rate limiting
API: CORS configreview CORSCORS misconfiguration
Mobile: hardcoded secrets/endpointsrg codeHardcoded secret (mobile)
Mobile: insecure storage APIsrg SharedPrefs/NSUserDefaultsInsecure storage
Mobile: exported components / URL schemesreview manifest/plistExported component
Mobile: WebView bridge / file accessrg addJavascriptInterfaceWebView misconfig
Mobile: cert pinning present & correctreview pinningPinning gap
Mobile: logging of sensitive datarg log + sensitiveSensitive log (mobile)

✓   Coverage map & how to review

Review sections 0–12 on every codebase, §13 only for C/C++/unmanaged, and apply the language gotchas that match the stack.

SectionReview onFocus
0–12Every codebaseAuth, authz, session, injection, XSS, crypto, secrets, errors, files, logic, deps, config
13 Memory safetyC/C++/unmanagedBuffer/format/integer/UAF
Language gotchasMatch to stackPer-language dangerous sinks
CategoryBy componentWeb backend / API / mobile focus

Core principle: code review is follow the data, don't just scan. SAST flags candidates; a finding is real only when you trace an untrusted source to a dangerous sink with no effective sanitisation between. Prioritise injection, auth/authz gaps, deserialization and hardcoded secrets — and confirm exploitability (or mark it for dynamic confirmation) rather than reporting raw scanner output.

Reactions

Related Articles