28 August 2026

8 Examples: What's Wrong with This Code?

Reza Khosravi
No items found.

Table of Contents

8 Examples: What's Wrong with This Code?

What's wrong with this code is usually not a syntax error. It's an unsafe assumption about input, trust, memory, identity, or secrets. A 2002 U.S. federal study estimated that buggy software cost the economy $59.5 billion annually, with better testing potentially reducing losses by about $22.5 billion per year (Computerworld's report on the study).

The popular advice is to fix the line that fails, rerun the test, and move on. That approach misses code that compiles, passes a happy-path test, and still exposes data, bypasses controls, or fails when an attacker controls the conditions. This review uses a more useful method: locate the untrusted input or missing boundary, trace the impact, choose a safer pattern, and turn the lesson into a rule that an IDE, threat model, test suite, or pull request policy can check repeatedly.

1. Null pointer exception and null reference error

A null reference error begins with an assumption that an object exists. The code then dereferences it without proving that assumption:

String country = user.getProfile().getAddress().getCountry();

This may work for a complete test fixture and fail when a new account has no profile, an upstream API omits address, or a transaction response contains an unexpected field. The immediate result is a crash or failed request. In an authentication, payment, or account-recovery path, repeated requests can turn that failure into a denial-of-service condition.

The security signal isn't the exception alone. Look for unvalidated data crossing a service boundary, especially when the failure path exposes stack traces, internal object names, account identifiers, or processing details. An authentication service that crashes when profile metadata is absent may also handle error states inconsistently, which makes it worth reviewing the surrounding authorization logic rather than adding one null check.

A sketched illustration of a laptop screen displaying a null pointer exception error in code.

Why the quick fix is insufficient

Scattering checks through the call chain can hide the contract. A default value such as an empty role, empty permission set, or zero transaction amount may prevent a crash while weakening a security decision. Treat null states as part of the design, not merely as runtime annoyances.

Use language and tooling features that make invalid states harder to represent:

  • Compile-time constraints: Enable Kotlin non-nullable types, TypeScript strict null checks, or Java's Optional where they clarify an absent value.
  • Explicit boundaries: Validate API responses and request objects when they enter the application, then pass typed values deeper into the system.
  • Meaningful failure handling: Return a safe, generic client error while recording enough internal context to investigate the event.
  • Automated detection: Run SonarQube, SpotBugs, or Checkmarx in development and CI, but review their findings in the business context.

Practical rule: If a missing value can change identity, privilege, payment state, or audit meaning, fail closed and record the security-relevant context.

Threat modeling should document which null states are ordinary and which indicate tampering or an upstream contract violation. For AI-generated code, require guard clauses and explicit failure behavior in the prompt and the repository template, then enforce the pattern during review.

2. SQL injection

SQL injection appears when the application treats data as query instructions:

query = "SELECT * FROM users WHERE email = '" + email + "'"

An attacker who controls email can alter the query's structure. Depending on the database account and endpoint, the exploit path may include authentication bypass, unauthorized reads, data modification, or destructive operations. The dangerous flow is direct: request input enters a string, the string becomes SQL, and the database interprets both as code and data.

A login form isn't the only entry point. Search, sorting, filtering, reporting, administrative exports, and background jobs can all construct queries unsafely. Reviewers should trace every value reaching a data-access layer, including values that appear to come from an internal service. Internal data can become attacker-controlled after a compromised account, manipulated webhook, or poisoned import.

Parameterization beats escaping

Parameterized queries separate the statement from its values. An ORM can help, but it doesn't automatically make every query safe. Raw-query helpers, dynamic table names, custom filters, and string-built ORDER BY clauses still require careful review.

Use a deliberate control set:

  • Parameterized values: Bind user-controlled values through the database driver or framework query API.
  • Allowlisted structure: Map sortable fields and selectable resources to known server-side identifiers instead of accepting arbitrary SQL fragments.
  • Least privilege: Give the application database account only the operations it needs, limiting the impact of a successful injection.
  • Review enforcement: Require a security review for data-access changes and flag concatenated SQL in the IDE and pull request.

The OWASP security guides provide useful secure-development material, while a practical secure code review guide helps teams connect the vulnerable data flow to review decisions and enforcement.

Detection still matters after deployment. Database activity monitoring can identify unusual query patterns, but it won't repair an unsafe query. The durable fix is a secure data-access template, a threat-model entry for each database boundary, and a Policy-as-Code rule that blocks unsafe construction before merge.

3. Cross-site scripting

Cross-site scripting occurs when the application sends attacker-controlled content to a browser in an executable context. The code may appear to be ordinary rendering:

results.innerHTML = searchParams.get("q");

If the value is reflected directly from a URL, the attack is reflected XSS. If a comment, profile field, or message is stored and rendered later, it becomes stored XSS. The exploit path can include script execution in another user's session, credential theft, unauthorized actions, phishing, or malicious content delivery.

The review question is not whether a field is “sanitized.” It is which output context receives the value. HTML text, an attribute, a URL, JavaScript, and CSS each have different encoding requirements. A sanitizer intended for HTML can be the wrong control for a JavaScript string or a URL.

A hand-drawn illustration depicting a SQL injection attack against a database server through a web URL.

Fix the rendering boundary

Prefer framework templating that escapes output by default. Avoid raw HTML rendering unless the content has a defined sanitization policy and the code review records why raw rendering is necessary. A Content Security Policy can reduce the impact of mistakes, but it isn't a substitute for correct output encoding.

A repeatable XSS control set includes:

  • Input mapping: Record which fields accept user input and where each field is rendered.
  • Contextual encoding: Encode at the output sink using a library designed for that context.
  • Template protection: Flag unsafe rendering methods in JavaScript, templates, and component code through SAST.
  • Browser defense: Enforce a restrictive CSP, including approved script sources and nonce-based handling where required.
  • Regression coverage: Add reflected and stored payload tests, then run automated browser or proxy scans such as OWASP ZAP in CI.

The security guidance for JavaScript applications is useful when the vulnerable sink sits in frontend code rather than the server template.

Stored XSS deserves special attention because one write can affect many later readers. Threat modeling should connect the input field, persistence layer, rendering surface, user role, and session impact. Policy checks can then require safe templating or an explicit reviewed exception instead of relying on a reviewer to notice innerHTML in every pull request.

4. Insecure deserialization

Deserialization becomes dangerous when untrusted bytes are converted into rich objects with behavior, constructors, or attacker-controlled fields. A line such as this can look like ordinary data loading:

model = pickle.load(uploaded_file)

In formats and libraries that support object construction or gadget chains, the parser may invoke behavior during loading. The exploit path can reach remote code execution, object manipulation, privilege escalation, or a malicious transaction state. Microservices increase the risk when serialized data crosses service boundaries and teams assume that “internal” messages are trusted.

The first review signal is an input source that reaches a general-purpose object deserializer. Search request bodies, message queues, cache entries, model files, session stores, and inter-service payloads. Then identify whether the format can execute behavior, instantiate arbitrary classes, or accept fields the receiving service never intended to process.

A four-step infographic illustrating the Cross-Site Scripting (XSS) attack flow from injection to data theft.

Use data formats with narrow behavior

JSON is not automatically safe, but it generally supports a narrower data model than native object serialization. The application must still validate types, ranges, required fields, and authorization meaning after parsing. A valid schema doesn't prove that the caller may perform the requested action.

For service boundaries:

  • Choose constrained formats: Prefer schema-driven formats such as JSON, Protocol Buffers, or Avro where they fit the architecture.
  • Allowlist types: If object deserialization is unavoidable, permit only known classes and reject unexpected fields.
  • Validate before use: Apply schema and business-rule validation before constructing domain objects or executing operations.
  • Control dependencies: Maintain an inventory of serialization libraries and scan dependencies for vulnerable behavior and gadget chains.
  • Constrain generated code: Tell coding assistants to use approved serialization helpers and reject suggestions involving unsafe native loaders.

A Policy-as-Code rule can block unsafe methods such as native object loaders in designated paths. That is stronger than documenting a preferred format because it checks the implementation at merge time. The threat model should also record every deserialization entry point, including paths that aren't exposed through a public HTTP endpoint.

5. Authentication bypass from weak or missing checks

Authentication bypass often hides behind a correct-looking endpoint. The handler may validate a token in one route but trust a user ID supplied to another:

app.get("/admin/report", (req, res) => {return sendReport(req.query.userId);});

If the route lacks authentication or authorization, a caller can invoke it directly. If it accepts a modified token without validating its signature, issuer, audience, expiry, or subject, the attacker may impersonate another identity. Password-reset flows can fail in similar ways when they reveal whether an account exists or accept a token that isn't bound to the intended user and action.

Review the whole identity path

Authentication proves who is calling. Authorization decides what that caller may do. Checking only the first step leaves object-level access controls exposed, especially in APIs that accept resource identifiers from the client.

Reviewers should ask:

  • Route coverage: Does every sensitive route require authentication by default, rather than relying on developers to remember an annotation?
  • Token validation: Does the service verify the complete token contract, including signature and relevant claims?
  • Object access: Does the server confirm that the authenticated principal may access the requested record?
  • Failure behavior: Do errors avoid account enumeration while still creating useful internal audit events?
  • Administrative separation: Are privileged operations protected by stronger controls and reviewed separately?

The safest default is to make unauthenticated access an explicit exception, not the absence of a check.

Policy enforcement can require authentication middleware or decorators on sensitive route groups. IDE analysis can flag handlers that read identity or resource identifiers without reaching an authorization decision. Penetration testing should exercise direct API calls, altered tokens, missing headers, reset flows, and inconsistent behavior between web and mobile clients.

In regulated applications, audit events should capture the user or service identity, endpoint, result, and time without recording sensitive credentials or tokens. Threat models should map each endpoint to its authentication mechanism and authorization rule, then link those decisions to the pull requests that implement them. A backlink about bypassing Akamai may be useful in a different operational context, but it doesn't replace application-level identity validation.

6. Buffer overflow and out-of-bounds memory access

Memory safety failures occur when code reads or writes outside the region it owns. In C or C++, a length calculation based on attacker-controlled input can overwrite adjacent memory, corrupt a function pointer, disclose data, crash a process, or create a path to code execution:

char destination[16];strcpy(destination, input);

The dangerous assumption is that input fits. The compiler may accept the code, and a normal test string may behave perfectly. An attacker supplies a longer or specially structured value, and the result depends on memory layout, compiler protections, and runtime conditions.

Managed languages avoid some memory corruption classes through bounds checks, but out-of-range access can still crash a service or expose information through flawed error handling. Native extensions, parsers, media processing, compression libraries, and network appliances deserve particular scrutiny because they process complex attacker-controlled data.

Test the boundary, not only the feature

Review the relationship between source length, destination capacity, integer conversion, allocation size, and terminator requirements. A “safe” replacement can still fail if the length is calculated incorrectly or if truncation changes a security decision.

Use layered controls:

  • Safer APIs: Block unsafe functions such as strcpy, gets, and sprintf, and use bounded interfaces with carefully checked lengths.
  • Dynamic analysis: Run AddressSanitizer, Valgrind, and equivalent tools during testing and CI.
  • Fuzzing: Feed malformed, oversized, truncated, and structurally unusual inputs to parsers and protocol handlers.
  • Compiler defenses: Use stack canaries, DEP, ASLR, and hardened build settings as mitigation, not as permission to keep unsafe code.
  • Memory-safe migration: For new components, consider languages with stronger memory-safety guarantees where their ecosystem and performance requirements fit.

Threat modeling should identify every untrusted byte source and the buffer or allocation it reaches. The review signal is a missing length invariant. Prevention requires that invariant to appear in code, tests, and merge policy, while runtime monitoring helps identify crashes that escaped pre-release testing.

7. Insecure cryptography

Cryptography fails when developers choose a weak algorithm, misuse a strong one, generate predictable keys, or omit authentication. The code may contain a familiar function and still provide no meaningful confidentiality or integrity:

digest = hashlib.md5(password.encode()).hexdigest()

A hash isn't a password-storage design just because it produces a digest. Passwords require a password-specific, deliberately slow hashing scheme with a unique salt and controlled verification process. Encryption has its own questions: What threat does it address? Where are keys generated and stored? Does the mode authenticate ciphertext? How are keys rotated and revoked?

Review the cryptographic contract

Don't approve cryptographic code based on the algorithm name alone. Record the data classification, required security properties, approved library, key lifecycle, and failure behavior in the design review. Application teams should use well-maintained library primitives rather than implementing encryption, randomness, padding, or signature verification themselves.

Useful controls include:

  • Approved primitives: Maintain an allowlist of current algorithms and library APIs, with deprecated functions blocked in pull requests.
  • Authenticated encryption: Use a standard construction that protects confidentiality and detects tampering.
  • Secret separation: Keep keys outside source code and separate key access from ordinary application data access.
  • Randomness validation: Use a cryptographically secure random source for keys, nonces, reset tokens, and session identifiers.
  • Traceable decisions: Connect cryptographic requirements to the data classification and the service threat model.

SAST tools can flag weak functions, but they won't know whether a key is reused across tenants or whether a nonce lifecycle is safe. A focused security design review catches those architectural errors. Coding assistants should receive approved cryptographic templates and be constrained from suggesting deprecated algorithms or custom implementations.

For regulated systems, the team must also verify the applicable organizational and sector requirements. Compliance references can guide the review, but passing a scanner doesn't establish that the chosen design protects the actual data flow.

8. Hard-coded secrets in source code

A hard-coded password, API key, token, private certificate, or connection string turns source access into an access path to another system:

STRIPE_KEY = "sk_live_example"

The secret may leak through a repository, build artifact, log, screenshot, package, fork, or developer machine. Removing the line in a later commit doesn't reliably erase the value from repository history or downstream copies. The first response should be revocation and rotation, followed by investigation of where the credential was used.

The review signal is any credential-like value in application code, test fixtures, configuration committed to version control, or generated files. Don't rely only on pattern matching. A scanner can miss a newly formatted token, while a false positive can train developers to ignore alerts.

Move secrets into an owned lifecycle

Use a secrets manager such as Vault or a cloud-native equivalent, retrieve credentials at runtime through an identity-controlled mechanism, and grant each workload only the access it needs. Local development should use safe placeholders or controlled secret injection, not production values copied into .env files.

  • Pre-commit detection: Run tools such as detect-secrets and Snyk in the IDE and before commits.
  • Pull request blocking: Add Policy-as-Code tools and workflows that stop suspected secrets from merging.
  • Immediate response: Revoke exposed credentials, assess access logs, and replace dependent configuration.
  • Rotation ownership: Automate rotation where possible and alert on credentials that outlive their intended purpose.
  • AI guardrails: Prevent coding assistants from inventing or repeating credential-shaped values, and direct them to approved secret APIs.

Threat modeling should list every external service, the identity used to access it, the permitted operations, and the rotation owner. A secret scanner detects a symptom. Prevention comes from architecture, short-lived credentials, repository controls, and a workflow that makes the secure path easier than embedding a string.

8 Common Code Vulnerabilities Compared

VulnerabilityImplementation Complexity 🔄Effort & Speed ⚡Security Impact 📊 / Criticality ⭐Recommended Remediation / Tips 💡
Null Pointer Exception (NPE) / Null Reference ErrorLow, code-level guard clauses or language featuresQuick, small code changes, static analysis (minutes–hours)High (⭐️⭐️⭐️⭐️)Add null checks/Optionals, enable strict null typing, CI static analysis, log null failures
SQL InjectionMedium, refactor query construction to parameterized patternsModerate, refactor data access layer, test DB interactions (days)Critical (⭐️⭐️⭐️⭐️⭐️)Use prepared statements/ORM, input validation, SAST, DB activity monitoring, Policy-as-Code
Cross-Site Scripting (XSS) – Reflected and StoredMedium, ensure context-aware encoding and CSPModerate, template/config updates, CSP rollout (days)High (⭐️⭐️⭐️⭐️)Apply contextual output encoding, enable auto-escaping templates, use DOMPurify, enforce CSP, include XSS tests
Insecure DeserializationHigh, design changes, allowlisting or schema enforcementSlow, replace unsafe serializers, add filters, inventory deps (weeks)Critical (⭐️⭐️⭐️⭐️⭐️)Prefer JSON/Protobuf, implement allowlists/serialization filters, SAST, schema validation, dependency scanning
Authentication Bypass – Weak or Missing ChecksMedium, centralize and enforce auth across servicesModerate, integrate auth frameworks, add middleware (days–weeks)Critical (⭐️⭐️⭐️⭐️⭐️)Use OAuth2/OpenID Connect, require auth middleware on all routes, secrets scanning, regular pen tests
Buffer Overflow / Out-of-Bounds AccessHigh, language/runtime changes or rigorous bounds checksSlow, add sanitizers, fuzzing, refactor unsafe code (weeks)Critical (⭐️⭐️⭐️⭐️⭐️)Use memory-safe APIs/languages, AddressSanitizer/fuzzing, block unsafe functions, enable compiler protections
Insecure Cryptography – Weak Algorithms or Improper ImplementationMedium–High, replace algorithms and improve key managementModerate–Slow, migrate ciphers, deploy KMS/vault (days–weeks)Critical (⭐️⭐️⭐️⭐️⭐️)Use vetted libs (AES-GCM, SHA-256), secure RNG, KMS/HashiCorp Vault, enforce crypto policy via CI
Hard-Coded Secrets – Passwords, API Keys, CredentialsLow–Medium, move secrets to vaults and purge historyModerate, rotate secrets, deploy secrets manager, scrub repo (days)Critical (⭐️⭐️⭐️⭐️⭐️)Use Secrets Manager/Vault, pre-commit hooks, secrets scanning, rotate and audit credentials

Turn code smells into reviewable security rules

The recurring question behind “what's wrong with this code” is not “which line looks ugly?” It is what assumption does this line make, and who can violate it? Trace attacker-controlled input through parsers, templates, query builders, identity checks, memory operations, cryptographic APIs, and secret stores. At every boundary, define the validation, authorization, encoding, memory, or key-management control that must hold.

The economic case for prevention is longstanding. The historical federal study linked software defects to large losses across users, vendors, and industry, and estimated that improved testing could prevent a meaningful share of those losses (historical software-defect cost analysis). Modern teams also face persistent security debt. Veracode's 2024 findings reported unresolved flaws in 80% of active applications and a typical application containing 42 flaws per 1 MB of code (Veracode State of Software Security 2024 summary). Those figures support a workflow that prevents recurring classes of defects instead of treating every finding as an isolated ticket.

Review latency is another control to measure. Security review latency means the delay between a code or configuration change and the point when governance or approval catches up. Track review coverage, aging, exception debt, and median time to review, with a starting target of 5 business days for critical systems described by security architecture guidance (security metrics and KPI guidance). A review that arrives after implementation, merge, and deployment may document risk without preventing it.

Modern code-review research also treats review length, response delay, and throughput as measurable indicators of how quickly reviewers catch defects and design flaws (empirical modern code-review study). That doesn't mean every change needs a meeting. It means risky changes need fast, contextual feedback inside the workflow where developers work.

AI-assisted development raises the stakes because generated code can be syntactically polished while carrying unsafe design assumptions. Independent reporting has described AI-generated code containing substantial vulnerability and design-flaw risk, including an enterprise report of more security findings alongside higher generated pull-request activity (analysis of AI coding risk). Coding assistants also create a new attack surface. Research summarized by the Cloud Security Alliance described prompt-injection exposure across tested AI-integrated development tools and possible paths to code execution or data exfiltration (CSA research on AI coding-assistant attack surfaces).

Turn these lessons into enforceable artifacts:

  • Secure templates: Parameterized queries, safe rendering helpers, constrained serialization, authenticated routes, bounded memory APIs, approved cryptography, and managed secret access.
  • Threat-model decisions: Record trust boundaries, input ownership, identity requirements, failure behavior, and approved exceptions.
  • IDE guardrails: Highlight unsafe sinks while the developer is writing code, including code produced by an assistant.
  • Automated tests: Exercise malformed input, missing fields, unauthorized resources, dangerous encodings, oversized buffers, and key-management failures.
  • Pull-request policies: Block prohibited functions and patterns, require review coverage for critical changes, and link findings to an approved design decision.
  • Implementation verification: Confirm that the merged code and deployment still match the security decision after dependencies, tickets, and architecture change.

DevArmor is one relevant option for keeping security context connected across design, coding, and review. Its stated platform capabilities include continuous threat modeling, workflow-based security design reviews, IDE and source-control integrations, Policy-as-Code enforcement on pull requests, and guardrails for AI-assisted coding. That model addresses the main weakness of late manual review, security decisions remain connected to the artifacts and changes that implement them, rather than living in a document nobody checks.


DevArmor connects continuous threat modeling and security design reviews with IDE, planning, and source-control workflows, then applies Policy-as-Code to pull requests. Visit DevArmor to see how your team can turn code-review findings into enforceable security decisions and repeatable remediation practices.

Table of Contents

Subscribe