A source is where untrusted data enters an application: a parameter, header, body field, or upload. A sink is the call that interprets it: a SQL query, shell command, template, URL fetch, or file path. An injection flaw is an unneutralized path between them. Five of the top ten 2025 CWE weaknesses have that shape.
Key takeaways
- Nearly every injection class is one model with a different interpreter at the end. OWASP puts it plainly: "The concept is identical among all interpreters."
- A sanitizer is only correct for the sink it protects. HTML escaping does nothing for a SQL query, and SQL escaping does nothing for a shell.
- The source of a stored or second-order bug is the request that wrote the data, not the database read that returned it.
- Evidence comes in three strengths: a path is reachable, untrusted data is shown to flow along it, or a request proved the sink misbehaves. Each answers a different question.
- The place a request entered the code is an address, not a diagnosis. The fix belongs where the value is used.
The vocabulary, in four words
Source. Any point where data the application does not control enters it: query strings, form fields, JSON bodies, headers, and cookies, but also file contents, webhook payloads, queue messages, and responses from other services. If someone outside the trust boundary can shape the value, it is a source.
Sink. A call that hands a value to something that interprets it: a database driver, a shell, an HTML renderer, a template engine, an HTTP client, the filesystem, a deserializer, an eval. A sink is not dangerous because of what it does. It is dangerous because it treats part of its input as instructions.
Propagation. The route between the two. Values get copied, concatenated, formatted, stored, and passed through helpers. The GitHub CodeQL documentation draws the useful line here: normal data flow follows values that are preserved at each step, while "taint tracking extends data flow analysis by including steps in which the data values are not necessarily preserved." A string built from a tainted string is still tainted.
Neutralization. Whatever stands between a source and a sink and makes the value safe for that sink: a parameterized query, an argument array with no shell, context-aware output encoding, an allow-list. The word is not decoration. It is in the formal name of most injection weaknesses, starting with CWE-89, "Improper Neutralization of Special Elements used in an SQL Command."
OWASP's own definition in A05:2025 Injection is the same model in one sentence: "an application flaw that allows untrusted user input to be sent to an interpreter (e.g. a browser, database, the command line) and causes the interpreter to execute parts of that input as commands." The category maps 37 CWEs, and the page notes more than 30,000 CVEs for cross-site scripting and more than 14,000 for SQL injection.
One model, many weaknesses
Change the sink and you change the vulnerability name. The source side barely moves. That is why the model is worth learning once instead of memorizing a list.
| Sink | What it interprets | Weakness | Neutralization that fits |
|---|---|---|---|
| SQL query | Query syntax | CWE-89 SQL injection | Parameterized queries; never string-built SQL |
| OS command | Shell metacharacters | CWE-78 OS command injection | Argument arrays to an API that does not invoke a shell |
| HTML response | Markup and script | CWE-79 cross-site scripting | Output encoding for the exact context: body, attribute, URL, script |
| Template engine | Template expressions | CWE-1336 template injection | Pass input as template data, never as template source |
| Outbound HTTP client | A destination | CWE-918 SSRF | Allow-list destinations; resolve and check before connecting |
| Redirect target | A destination for the user | CWE-601 open redirect | Relative paths or an allow-list of hosts |
| Filesystem path | Directory traversal | CWE-22 path traversal | Canonicalize, then confirm the result stays under the base directory |
| Upload storage | A file the server may execute or serve | CWE-434 unrestricted upload | Validate type server-side; store outside the web root |
| Code evaluation | Program code | CWE-94 code injection | Do not evaluate input; there is no safe escaping for it |
| Deserializer | Object graphs | CWE-502 unsafe deserialization | Data-only formats; no native deserialization of untrusted bytes |
The 2025 CWE Top 25 shows how much of the list this one model explains. Cross-site scripting is first, SQL injection second, path traversal sixth, OS command injection ninth, and code injection tenth. Five of the top ten are a source reaching a sink. Unrestricted upload, unsafe deserialization, and SSRF appear further down the same list.
Why the sanitizer has to match the sink
Here is the whole model in three lines of Python:
name = request.args["name"] # source
query = f"SELECT * FROM users WHERE name = '{name}'" # propagation
cursor.execute(query) # sink
The fix is not to clean name at the top. It is to change the sink call so the value can never become syntax:
cursor.execute("SELECT * FROM users WHERE name = %s", (name,))
Sanitizing once at the source assumes you know every sink the value will ever reach. You do not. The same string can land in a query, an HTML page, and a filename, and each needs a different treatment: escaping HTML at the door leaves SQL open, and escaping for SQL leaves a shell open. Validation at the source is still worth doing (reject what is plainly wrong in shape), but the neutralization that matters is applied at the sink, for that sink.
The same logic explains stored and second-order bugs. A value is written safely through a parameterized query, sits in the database, and is later read back and concatenated into a different query, or rendered into a page with no encoding. The database read is not the source. The request that wrote the value is. Tracing only within one request misses this entire class, which is why the question to ask about any stored field is "who could have written this?"
Three levels of evidence
A source-to-sink claim can be made at three strengths, and they are easy to blur.
1. Reachable. There is a code path from an entry point to a sink. This is a map, not a verdict. It says where injection could physically land, and it says nothing about whether untrusted data arrives there intact.
2. Tainted. Data flow analysis shows untrusted data can travel from a source to that sink without passing a recognized neutralization. Stronger, but still a model of the code. It can flag paths no real request can drive, and it can miss flows through reflection, framework magic, or another service.
3. Runtime-proven. A request was sent to the running application and the sink behaved differently because of it: a database error, a timing difference, an out-of-band callback, a payload rendered as script. This is the strongest evidence that the flaw is real and exploitable as deployed. Its limit is the mirror image of static analysis: it proves only the paths the test actually reached.
None of the three replaces the others. OWASP's injection guidance says as much: "Detection is best achieved by a combination of source code review along with automated testing (including fuzzing) of all parameters, headers, URL, cookies, JSON, SOAP, and XML data inputs." Reachability tells you where to look, taint tells you what is likely, and a runtime test tells you what is true.
The entry point is an address, not a diagnosis
When a finding points at a handler, it is pointing at the source side: the place the request entered the code. That is valuable, because the alternative is searching a repository for a class of bug. But the handler is rarely where the flaw lives. The flaw lives at the sink, often a call or two deeper, in the service or repository method that takes the parameter and builds the query.
The practical routine is short. Open the reported entry point. Find the parameter the finding names. Follow it forward until it reaches something that interprets it. Fix the sink call, then check whether any other source reaches the same sink by a different route. A patch at the handler that validates one parameter leaves every other caller of that sink exposed.
Frequently asked questions
What is the difference between a source and a sink in application security?
A source is where untrusted data enters: a request parameter, header, cookie, body, file, or message from another system. A sink is a call that interprets data as instructions, such as a SQL query, shell command, template, or outbound URL. An injection vulnerability is a path from one to the other without adequate neutralization.
What is taint analysis?
Taint analysis marks data from sources as tainted and follows it through the program, including through transformations like concatenation and formatting, to see whether it reaches a sink without a sanitizer. It is how static analysis tools find source-to-sink paths without running the code.
Should I sanitize input when it arrives or when it is used?
Validate shape when it arrives, but neutralize where it is used. Only the sink knows what "safe" means: a parameterized query for SQL, context-aware encoding for HTML, an argument array for a process call. One sanitizer at the front door cannot be right for every sink a value reaches.
Is data read from my own database a trusted source?
Not if anyone outside the trust boundary could have written it. Stored cross-site scripting and second-order SQL injection both start with a value that was stored safely and later used unsafely. Trace the value back to whoever wrote it.
Why does SSRF count as a source-to-sink bug?
Because the sink is the outbound HTTP client and what it interprets is a destination. When a request can choose the host or path the server connects to, untrusted data has reached a sink with network reach the attacker lacks. CWE-918 covers it.
Does a reachable vulnerable library function mean I am exploitable?
Not by itself. Dependency reachability asks whether your code calls the vulnerable function: a sink-shaped question with no source attached. A function you only ever call with a hard-coded constant is reachable and still not exploitable. Answer the source question before ranking the finding.
Where to read next
Sources and sinks are the grammar; individual weaknesses are sentences in it. Server-Side Request Forgery (SSRF), Explained walks one sink end to end, and the GeoServer SQL injection write-up shows the model in a real advisory. For where each kind of testing sits in a program, read What You Should Know About Application Security Testing.