"The code is correct!" is one of the most dangerous sentences in software engineering, because correctness and security answer two completely different questions. A function can pass every unit test, satisfy the spec, and survive review while still handing an attacker a way in. Correctness asks "does this do what we intended for the inputs we expected?" Security asks "what happens for the inputs we never expected, from someone who is actively hostile?" The two overlap, but they are not the same, and treating a green test suite as proof of safety is how vulnerabilities reach production.
This post walks through the places where perfectly correct code is still exploitable, so you can stop trusting "it works" as a security signal.
Correctness is about intent, security is about abuse
When we say the code is correct, we usually mean it produces the right output for a defined set of inputs. That is a statement about the happy path and a handful of edge cases the author thought about. Security failures almost always live outside that set.
Consider a login handler that correctly authenticates valid users and correctly rejects wrong passwords. It is functionally correct. But if it takes measurably longer to reject an unknown username than a known one, it leaks account existence through a timing side channel. No test caught it because no test measured timing. The behavior matches the spec; the spec never mentioned an attacker with a stopwatch.
The lesson is that correctness is validated against requirements, and requirements are written by people who are imagining normal use. Attackers do not read your requirements.
The classic gap: input the author never imagined
Most exploitable-but-correct code fails on inputs the author considered impossible or irrelevant. A few recurring patterns:
- A path-building routine that correctly joins a base directory and a filename, but never anticipated a filename of
../../etc/passwd. - A search feature that correctly interpolates a user's query into a database call and returns matching rows, without considering that the query itself might be
'; DROP TABLE users;--. - A template renderer that correctly substitutes variables, but happily evaluates attacker-supplied expressions when the variable contains template syntax.
In each case the code does exactly what it was written to do. The defect is not a logic error; it is a missing trust boundary. Correctness testing checks that the function honors its contract. It does not check whether the contract was safe to offer in the first place.
"It compiles and the tests pass" proves the least interesting thing
Tests encode expectations. They are excellent at catching regressions and logic mistakes, and nearly useless at catching vulnerabilities you did not think to write a test for. Nobody writes a unit test titled "does not allow authorization bypass via negative array index" unless they already suspected the bug existed.
This is the core asymmetry: a passing test proves a specific behavior for a specific input. Security requires reasoning about the complement — the infinite set of inputs and states you did not enumerate. That is why static analysis, fuzzing, and dependency scanning matter. Fuzzers generate the weird inputs your test suite never will. A software composition analysis scan flags a correct call into a library function that happens to carry a known CVE. Dynamic testing with a tool like DAST exercises the running app the way an attacker would, not the way your tests do.
Correct code, vulnerable dependency
Here is a case where your code is genuinely, completely correct and you are still exposed: you call a well-documented library function exactly as intended, and that library version has a known vulnerability.
// Perfectly correct usage
const parsed = yaml.load(userSuppliedConfig);
If the pinned version of that YAML library deserializes arbitrary objects by default, this correct line is a remote code execution primitive. Your code is not wrong. Your dependency graph is. This is why "the code is correct" can never be a whole-system security claim — most of the code running in your process was not written by you. An SCA tool such as Safeguard can flag this transitively, pointing at the vulnerable version buried three levels down in your lockfile, even though your own line of code reads exactly as the docs recommend.
Correctness under concurrency is a different animal
Single-threaded correctness says nothing about what happens when two requests race. A check-then-act sequence — verify a user has balance, then deduct it — is correct in isolation and exploitable under concurrency, letting an attacker spend the same balance twice by firing simultaneous requests. This class of bug (a time-of-check to time-of-use race, cataloged as CWE-367) passes every serial test because serial tests never interleave. The code is correct one call at a time and wrong in aggregate.
How to stop trusting "it works"
Shift the question from "does it work?" to "how could this be abused?" Practical habits that close the gap:
- Threat model the trust boundaries, not the functions. Ask where untrusted data enters and where it reaches something dangerous (a shell, a query, a filesystem path, a deserializer).
- Treat every external input as hostile by default. Validate against an allowlist, encode on output, and parameterize anything that touches an interpreter.
- Add negative and adversarial tests. For each feature, write at least one test that supplies malicious or malformed input and asserts the safe failure mode.
- Automate what humans miss. SCA for dependencies, SAST for code patterns, DAST for runtime behavior, and fuzzing for the inputs nobody imagined.
- Review for security separately from review for correctness. They use different mental models; combining them into one pass means the louder one wins, and correctness is always louder.
The goal is not paranoia. It is recognizing that "the code is correct!" is a true and useful statement about behavior — and a dangerously incomplete statement about safety. If you want to go deeper on building this instinct, the Safeguard Academy has walkthroughs on the common weakness patterns.
FAQ
Does correct code mean secure code?
No. Correctness means the code behaves as specified for expected inputs. Security concerns behavior under unexpected, malicious, or concurrent conditions the specification never covered. Code can be fully correct and still contain exploitable vulnerabilities, especially around untrusted input and vulnerable dependencies.
Why don't unit tests catch security bugs?
Unit tests verify expected behavior for inputs the author chose. Most vulnerabilities live in inputs and states the author never considered, so no test exists for them. Fuzzing, static analysis, and dynamic testing are designed to explore that unconsidered space.
If my code is correct, how can a dependency make it vulnerable?
Most code in a running application comes from third-party libraries. Calling a vulnerable library exactly as documented still exposes you to that library's flaws. Software composition analysis maps your dependency tree and flags known vulnerable versions even when your own usage is textbook-correct.
What's the fastest way to find security gaps in "working" code?
Combine automated scanning (SCA for dependencies, SAST for code, DAST for the running app) with adversarial test cases and a threat-modeling pass focused on where untrusted input crosses into dangerous operations.