What Is a Vulnerability, Really? Source, Sink, and Taint

What Is a Vulnerability, Really? Source, Sink, and Taint

# security# beginners# java# webdev
What Is a Vulnerability, Really? Source, Sink, and TaintAli Afana

Two Java methods. One of them will let an attacker delete your entire products table. The other is...

Two Java methods. One of them will let an attacker delete your entire products
table. The other is completely safe.

public int deleteA(HttpServletRequest request, Connection conn) {
    String id = request.getParameter("id");
    String sql = "DELETE FROM products WHERE id = " + id;
    return conn.createStatement().executeUpdate(sql);
}

public int deleteB(HttpServletRequest request, Connection conn) {
    String id = request.getParameter("id");
    if (!id.matches("[0-9]+")) {
        throw new IllegalArgumentException("id must be numeric");
    }
    String sql = "DELETE FROM products WHERE id = " + id;
    return conn.createStatement().executeUpdate(sql);
}
Enter fullscreen mode Exit fullscreen mode

Four lines different. If you can already see which is which and why, you know
more security than most working developers.

If you can't — that's what this article is for. By the end you'll be able to look
at almost any injection vulnerability and describe exactly what's wrong with it,
using three words.

I'm building an automated vulnerability scanner, in public. Those three words
are the entire foundation it's built on.


The three words

Nearly every common web vulnerability has the same shape:

The program takes input from the user and uses it somewhere dangerous,
without cleaning it first.

Three terms capture that:

SOURCE — where outside input enters your program. request.getParameter("id")
reads something a user typed. Since anyone can be that user, you must assume the
worst possible value.

SINK — an operation that becomes dangerous with the wrong input.
executeUpdate(sql) hands a string to your database and says "run this."

TAINT — the idea that untrusted data stains everything it touches. Input
arrives tainted. Copy it into a variable, that variable is tainted. Concatenate it
into a bigger string, the whole string is tainted. The stain spreads.

The tap and the glass

Picture a tap that might be running dirty water — that's your source. Picture
a glass you're about to drink from — that's your sink. Taint is the dirt.

A vulnerability is when dirty water flows from the tap to the glass with no
filter in between
.

That's it. That's the whole model.

Now look at the two methods again:

  • deleteA — tap → glass, nothing in between. Vulnerable.
  • deleteB — tap → filter → glass. The matches("[0-9]+") check rejects anything that isn't pure digits. Safe.

What the attack actually looks like

For deleteA, a normal request sends id=42:

DELETE FROM products WHERE id = 42
Enter fullscreen mode Exit fullscreen mode

Fine. One product deleted.

Now an attacker sends id=42 OR 1=1:

DELETE FROM products WHERE id = 42 OR 1=1
Enter fullscreen mode Exit fullscreen mode

1=1 is true for every row. Your entire products table is gone.

The attacker didn't break into anything. They typed text into a field you gave
them. Your code took that text and made it part of a command.

That's the thing worth sitting with: injection bugs aren't about breaking in.
They're about your program treating a stranger's text as instructions.

In deleteB, 42 OR 1=1 fails the [0-9]+ check and the method throws before
any SQL is built. Same tap, same glass, but the filter catches the dirt.


The same shape, four different bugs

Here's why this model is worth learning: once you see it, four of the most common
vulnerability classes collapse into one idea with different taps and glasses.

Vulnerability The dangerous sink What an attacker gets
SQL injection (CWE-89) a database query reads or destroys your data
Command injection (CWE-78) running a system command runs any program on your server
Path traversal (CWE-22) opening a file by name reads files they shouldn't see
XSS (CWE-79) writing into a web page runs code in your other users' browsers

Different sinks, identical shape. Untrusted input reaches a dangerous operation
with nothing neutralising it on the way.

What's a CWE number? The Common Weakness Enumeration is a worldwide
catalogue that numbers every type of software weakness. "CWE-89" means SQL
injection everywhere on earth. It matters because it lets different tools,
written by different companies, talk about the same bug — which is what makes
the tool comparison later in this series possible at all.

Path traversal, for example, is the same story with files:

String name = request.getParameter("file");
File f = new File("/var/data/" + name);     // sink
Enter fullscreen mode Exit fullscreen mode

Send file=report.pdf and you read a report. Send file=../../../etc/passwd and
you walk up out of the directory and read the system password file. Tap, glass,
no filter.

Why this is hard to automate

Here's where my project starts.

A scanner can trace the path — that's mechanical. Follow the data from
getParameter through every variable it touches until it reaches
executeUpdate. If a path exists, flag it. That technique is called taint
analysis
and it's what most security scanners do.

But run that on the two methods at the top, and it flags both.

Because in deleteB, the data genuinely does flow from source to sink. The
matches("[0-9]+") line doesn't break the path — id is still the same variable,
still reaching the same query. What that line changes isn't the route, it's the
meaning: after it, id can only be digits, so the attack is impossible.

A path-tracing tool sees connectivity. It cannot see meaning.

So real scanners face an ugly choice: hand-code a list of every function that
counts as a filter (impossible to keep complete — every library has its own), or
report the flow anyway and let developers sort it out.

Most choose the second. That's why security tools have a reputation for crying
wolf.


Going deeper (skip if you just want the lesson)

The industry's own test suite makes this concrete. The OWASP Benchmark is a set
of Java test cases with known answers, used to grade scanners. In the four
categories I detect, it contains 1,478 cases: 777 real vulnerabilities and
701 that are deliberately built to look vulnerable while being safe
— exactly
the deleteB pattern, using real filters like numeric allow-lists, character
stripping, and encoders.

Forty-seven percent of the test set exists purely to punish tools that can't
tell meaning from connectivity. That ratio isn't an accident — the benchmark's
designers made exactly this distinction the core test.

What my scanner does about it

The approach I'm testing: let the mechanical part stay mechanical, and hand the
meaning question to something that can read code.

Fixed rules trace every path from source to sink — reproducibly, missing nothing,
and deliberately over-reporting. Then a language model looks at each flagged
snippet on its own and answers one question: is this actually exploitable?

On my test project it flagged 6 candidates and the AI rejected 2 of them. Here's
its verbatim reasoning on the deleteB-style case:

"The input 'id' is validated against a numeric-only regex '[0-9]+' before
being concatenated into the SQL query. This allow-list guard prevents any SQL
injection characters from reaching the sink."

That's the judgement the path-tracer structurally cannot make. Whether that holds
up at scale — across 1,478 cases, measured against Semgrep and CodeQL — is what
the rest of this series is about.

What I learned

1. Most vulnerabilities are one sentence. Untrusted input reaches a dangerous
operation without being neutralised. Learn to spot the tap, the glass, and whether
there's a filter, and you can read most security findings without memorising bug
categories.

2. "There's a path" and "there's a bug" are different claims. This trips up
beginners and tools alike. The path is necessary but not sufficient. Always ask
what happens to the data on the way.

3. The hard part of automation is never the searching. Tracing paths is
mechanical. Deciding whether a filter actually filters is judgement — and that
distinction shaped my entire project.

Next in this series: what happened when I told the AI a scanner had flagged the
code — and it agreed with everything.


I'm Ali Afana — AI builder and security researcher, writing from Gaza. I
build systems in public, measure them against ground truth, and keep the
receipts. This scanner is one project on a longer road — follow for what
comes next.

GitHub · X · LinkedIn