When a security tool reads your code, it keeps asking one quiet question: when I see the name x, which object in memory does it stand for right now? That question is what points-to analysis answers, and getting it right is the difference between a real bug and a wasted afternoon chasing one that was never there. This post is about heap and alias reasoning only, the part that decides whether two names touch the same thing.
What points-to analysis actually computes
A variable in source is just a label. The object it refers to lives on the heap, and one object can wear many labels at once. Points to analysis builds a map: for each variable or pointer at each point in the program, which set of objects could it be holding. If the set has one object, the tool knows exactly what you are touching. If the set has ten, the tool is honest that it is not sure.
Here is the plain version:
- An object is a thing on the heap, usually named by where it was created, like “the dict made on line 12”.
- A variable points to a set of those objects.
- Two variables alias when their sets overlap, meaning they can name the same object.
Aliasing is the whole game. If you write to memory through one name, every other name that aliases it sees the change too. Miss that and your model of the program is wrong.
Aliasing: two names, one object
Look at this Python snippet:
profile = load_profile(request) # object A, tainted
view = profile # view now points to object A too
view.bio = escape(view.bio) # sanitize through 'view'
html = render(profile.bio) # use through 'profile'
Does the last line ship raw user input into your HTML? It depends entirely on whether view and profile point to the same object. They do here, because view = profile copies the reference, not the object. So view.bio and profile.bio are the same field. The escape call cleans it, and the render is safe.
Now change one line:
profile = load_profile(request) # object A, tainted
view = copy_profile(profile) # object B, a fresh copy
view.bio = escape(view.bio) # sanitize object B
html = render(profile.bio) # still object A, still tainted
Same shape, opposite verdict. Now view points to object B and profile still points to object A. The sanitizer cleaned the copy, and the render sends the untouched original straight into the page. That is a real cross site scripting bug.
The two snippets read almost identically. Only the heap tells them apart, and only a tool that tracks which object each name holds can call one safe and the other a bug.
A tool without alias reasoning has to guess. Guess that the sanitize always counts and it misses the second bug. Guess that it never counts and it screams about the first one, which is fine. That guessing is where a lot of scanner noise is born.
Why this decides real from false
Injection findings all have the same skeleton: tainted input reaches a dangerous sink. The catch is that the value often passes through several names, assignments, and function calls on the way. A sanitizer might sit on one of those names. Whether it protects the value at the sink comes down to a single question: is the sanitized name the same object as the one that reaches the sink? Answer it wrong and you either report a clean path as vulnerable or wave a live bug through. Both are expensive.
Flow sensitivity and context sensitivity: accuracy versus cost
Points to analysis comes in grades. The two knobs that matter most are flow sensitivity and context sensitivity, and both trade precision for compute.
Flow sensitivity
A flow insensitive analysis ignores statement order. It says “across this whole function, x can point to any object it ever pointed to.” Cheap, but sloppy. Consider:
x = safe_object()
use(x) # x is safe here
x = tainted_object()
use(x) # x is tainted here
A flow insensitive tool merges both assignments and decides x might be tainted at the first use too, which is false. A flow sensitive tool respects the timeline: safe at line 2, tainted at line 4. More accurate, more memory, more time, because it now tracks the map at every point rather than once per function.
Context sensitivity
Context sensitivity is the same idea across function calls. When one helper is called from two places, does the analysis keep the calls apart or blur them together?
def wrap(v):
return Box(v)
a = wrap(user_input()) # Box holds tainted
b = wrap(config_value()) # Box holds safe
A context insensitive tool analyzes wrap once and merges its callers, so it thinks both a and b might hold tainted data. A context sensitive tool treats each call site on its own and keeps b clean. The precision costs you: the analysis effectively re examines the callee per context, and contexts multiply fast on a large codebase. Every serious tool picks a budget, some bounded amount of context, and lives with the approximation past it.
Points-to analysis and the false positive problem
Here is the security payoff. A finding that says “tainted value reaches a sink” is only trustworthy if the tool knows the tainted name and the sink name are really the same object. When it cannot tell aliases apart, it falls back to conservative guessing, and conservative guessing on the heap is a top source of false positives.
Picture a request handler that stashes user input in a shared dictionary, hands one entry to a validator, and passes a different entry to a query builder. A weak analysis sees “user input went into the dictionary, dictionary data reached the query” and files a SQL injection report. A tool with sharper heap reasoning sees that the validated entry and the queried entry are separate objects, and that the queried one was never tainted. One less false alarm, one more reason a developer keeps trusting the tool.
This is why heap reasoning is not an academic detail. It is the machinery that separates a genuine tainted path from a coincidence of names. If you want the bigger picture of why static tools cry wolf, read why SAST has false positives. Points to analysis is one of the graph layers that sits underneath a modern representation of code, described in what is a code property graph.
Where the limits are
No analysis nails the heap perfectly, and honest tooling admits it. Dynamic dispatch, reflection, function pointers, and data that arrives from outside the program all force the analysis to widen its guesses. The practical answer is not to demand a perfect map but to know when the map is fuzzy and treat those findings with extra care, human review included.
That gap between what a name says and what the heap actually does is exactly the kind of thing an autonomous researcher that reasons about how an app really behaves is built to check, rather than trusting the pattern at face value. If that is the way you want your code looked at, here is who we are, and here is more on scanners versus research.
Frequently asked questions
What does points-to analysis do?
It computes, for each variable or pointer at a point in the program, the set of heap objects it could refer to. A single object means the tool knows exactly what you are touching, while a large set means it is uncertain. Security tools use this map to decide whether a tainted value and a sink really name the same object.
What is aliasing and why does it matter for a bug?
Aliasing is when two names point to the same object, so writing through one name is visible through the other. It decides real bugs from false ones. If you sanitize a value through one reference but use it through an alias, the fix only counts when both references point to the same object. When they point to separate objects, the original stays tainted and the bug is real.
What is the difference between flow sensitivity and context sensitivity?
Flow sensitivity respects statement order, so a variable that is safe on one line and tainted on the next is tracked as two different states. Context sensitivity keeps calls to the same function separate, so a helper called with safe data in one place and tainted data in another does not get merged. Both raise accuracy and both cost more compute, so tools pick a budget.
How does heap reasoning cut false positives?
A tainted value often passes through many names before it reaches a sink. If the analysis cannot tell aliases apart, it guesses conservatively and reports paths that are actually safe. Sharper heap reasoning sees that a validated object and a queried object are separate, so it drops the false alarm and only reports paths where a tainted object truly reaches the sink.
Put an autonomous researcher on your own systems
UnboundCompute is an autonomous security researcher that reasons about how an application fits together and proves the access control and injection bugs it finds. We are opening a small number of founding design partner seats: private early access pointed at a staging target you choose, and a say in what it looks for. If your team ships software worth pressure testing, apply to the design partner program.
