To review an injection claim, trace one value from a person or system that can influence it to an operation that interprets a language: SQL, a shell command, a template, browser HTML, or another parser. Record every transformation and control on that path. The decisive question is whether the value remains data or can change the operation's instructions. A dangerous-looking API call is a lead, not a complete vulnerability. A safe-looking validator is evidence only if the relevant path actually uses it. The answer can change with the caller, deployment, parser configuration, or an unseen wrapper.
This method gives an AppSec reviewer a concrete decision: reject a claim when the relevant boundary holds, investigate a named missing edge, or open a repair for a supported path. A straightforward fix such as preserving parameter binding need not wait for a live exploit; describe separately what the source establishes and what runtime validation would add. Source review alone does not establish that anyone executed an exploit. OWASP's injection prevention guidance and MITRE's template injection definition describe different interpreters, but the review question is the same: who controls the input, and where might it become syntax?
Draw the path before judging the line
Use a six-part path: actor → controlled value → entry point → transformations → interpreter → effect. Put the intended control at the exact edge where it acts. This prevents two common review errors. One is treating a public request parameter as if it goes straight to a database because a scanner linked two nearby lines. The other is accepting a validation helper found somewhere in the repository without showing that this request calls it before the sensitive operation.
For each candidate, pin the repository revision and the selected review scope. Trace the production route, background event, API client, import, or job that reaches the operation. Inspect wrappers and middleware, not just the flagged function. If source is missing, a generated query cannot be recovered, or deployment routing is unclear, write down that limit. An inventory entry proves that a file was found; it does not prove that its behavior was examined.
Different interpreters require different boundary evidence:
| Operation | What the reviewer must locate | Stronger control evidence |
|---|---|---|
| SQL query | The exact statement shape and each bound value or dynamic identifier | Values bound through the driver; any dynamic column or sort token chosen from fixed server-side choices. |
| OS process | The executable, argument list, shell setting, and authority of the child process | No unnecessary shell; fixed executable and flags; user choices mapped to approved arguments. |
| Server template | The source compiled as a template and the values passed for rendering | Trusted template source selected by a fixed key; untrusted text passed as a value. |
| Browser output | The actual HTML, attribute, URL, script, or DOM sink | A safe text sink, context-aware encoding, or maintained sanitization when HTML is genuinely required. |
Parameterized SQL separates bound values from query text, but a parameter cannot stand in for an arbitrary column name or sort direction; redesign or a fixed allowlist is needed there. OWASP's SQL injection guidance makes this distinction. A process argument list avoids implicit shell parsing in Python, but it does not prove that the called program treats each argument harmlessly; OWASP's command injection guidance discusses argument injection as a separate concern. For browser output, encoding must match the final context rather than be applied once at request intake, as OWASP's XSS guidance explains.
One report title, four different interpretations
Synthetic example; no query, renderer or browser was run. Mira, an ordinary Cedar member, names a report Q4 <b>forecast</b>. The title travels through four destinations. It is deliberately harmless: the angle brackets make the data/code distinction visible without establishing an exploit.
POST /reports → stored title
├─ search: SQL parameter
├─ notification: template variable
├─ activity preview: converted HTML
└─ audit event: JSON field → operator viewer
A scanner combines these edges into “stored injection throughout reporting.” That is too broad. Storage preserves a value; it does not give every later use the same interpretation.
At the database edge, the application passes two separate arguments:
statement = "SELECT id FROM reports WHERE tenant_id = ? AND title = ?"
parameters = (authorized_tenant_id, title)
rows = db.execute(statement, parameters)
The title occupies a value slot; it cannot become the statement's SQL structure through this binding API. The account predicate is visible, but whether authorized_tenant_id is genuinely authorized remains a separate question. A later export building SQL by concatenation would need its own trace. The useful rejection identifies this driver call and this title path, rather than claiming all report queries safe. See the SQLite parameter guidance and the SQL-specific review.
The notification uses a fixed file and a value:
html = env.get_template("report-created.html").render(title=title)
If the deployed template uses an ordinary HTML text position with autoescape enabled, the title is displayed as text. Its characters are not recompiled as template source. If a helper instead constructs template source by concatenating the title before compilation, the server-side conclusion changes. If the template disables escaping, the browser conclusion changes. Those are two different boundaries, explained in the template review.
The preview creates HTML and assigns it to a raw HTML sink. Here the reviewer finds conversion but cannot find the sanitizer implementation. The correct output is unresolved preview path, with the conversion adapter as the missing artifact. The source-supported facts establish that user content reaches HTML parsing; they do not establish which HTML survives an unseen control or what a viewer could be made to do.
Finally, JSON logging may preserve one event safely while an operator viewer later reconstructs HTML from the decoded title. The log writer and viewer deserve separate decisions. A safe producer cannot transfer its guarantee to a different interpreter downstream.
Let new evidence change exactly one conclusion
The owner supplies the missing preview helper:
convert Markdown → sanitize to documented rich-text policy → HTML sink
That supports the preview control only if the reviewed dependency version, policy and actual route match, and no later transformation reintroduces markup. Now suppose the export viewer adds a link decorator after sanitization. Reopen that viewer's path; the SQL binding and fixed notification template remain valid counterevidence for their own paths. OWASP warns that modifying sanitized content can invalidate the protection.
The resulting review is useful because it is divisible:
| Destination | Disposition from the synthetic packet | What would change it |
|---|---|---|
| SQL search | Reject title-to-SQL syntax claim | Adapter interpolates instead of binding, or another caller assembles text |
| Notification | Reject template-source claim; inspect HTML context separately | Title inserted before compilation, or different template/environment |
| Activity preview | Control supported after helper inspection | Different sanitizer policy/version or mutation afterward |
| Decorated export viewer | Investigate new output boundary | Exact decorator and final sink prove safe construction |
| Operator log viewer | Not assessed | Viewer implementation and its final output context |
Write the repair at the boundary that failed
A generic instruction to “sanitize the title” is weaker than the evidence. It could strip useful characters without fixing the export decorator, and it could leave a second field untreated. If titles are plain text, keep them as text at display sinks. If a feature intentionally supports rich text, apply the documented HTML policy after conversion and preserve it through delivery. Keep SQL values bound and template source application-owned independently.
The reader should finish with a small set of claims that another reviewer can reopen: a pinned revision, source actor, exact transformation chain, interpreter, effective control and bounded effect. A missing adapter gets an owner; a rejected path retains its counterevidence; a supported path advances through the organization's verification process. Authorized synthetic validation can supplement those decisions, but this example contains no performed validation.
Track transformations without assigning one global trust label
Mira's title can pass several legitimate transformations before reaching a destination. A serializer may encode it for transport, a parser may decode it again, and a display helper may convert part of it to markup. None of those names by itself tells the reviewer whether the final interpreter receives data or instructions. Write the actual representation at each important edge.
For the synthetic title, the stored value contains angle brackets. JSON transport represents that value according to its own syntax. After decoding, the frontend again holds a string containing angle brackets. A safe text sink treats those characters as text; an HTML sink asks a different parser to interpret them. The useful evidence is the final sink and its applicable control, not the fact that an earlier layer successfully serialized the value.
This also explains why a field can be safe in one destination and require a repair in another. The SQL search has a statement-and-values boundary. The notification has a template-source boundary and an HTML-output context. The activity preview deliberately permits some rich text. The review should give each destination a distinct control argument rather than assigning the title a permanent label such as “sanitized.”
Investigate stored values as new inputs to later work
A background export may read the title hours after the original request. Trace the actor who could set or change the stored value and the authority of the later consumer. The delay does not remove the original influence. At the same time, do not assume every database field is attacker-controlled: inspect who can write it, which transformations persist, and whether the worker selects the same record.
A useful extension of the path record adds the persisted field, its writer, and its consumer. For example: ordinary Cedar member writes report.title; the database stores the title as data; an export decorator reads it into a display component. If the decorator is missing from the source packet, that is the exact evidence request. The original SQL binding remains relevant to storage integrity but cannot settle the later browser interpretation.
Inspect fallback and error branches too. A normal path may return text safely while an error page embeds the original title into a different context. A failed conversion may invoke an older rendering helper. These are hypotheses to check in the actual code. The review should not claim such branches exist without source evidence, but it should avoid stopping at the first safe-looking happy path.
Keep the developer's repair smaller than the broad alert
For the decorated export viewer, a useful ticket names the exact transformation that needs correction and the permitted feature behavior. If the viewer should show plain text, use a text-oriented construction. If it supports rich text, define the permitted output and inspect the final transformation after sanitization. The ticket should not demand removal of harmless punctuation from every report title across the application.
Acceptance evidence should include the valid user behavior the change must preserve. Mira's title should remain searchable as a value and display according to the intended plain-text or rich-text policy. A repair that strips every title to an empty string may suppress a scanner pattern while breaking the product. The reviewer needs the safe behavior and the failed boundary in the same record.
The final disposition can therefore contain several different outcomes without contradiction: a rejected SQL claim, a supported template-source separation, a preview control needing version qualification, and an unreviewed log viewer. That is a more useful result than forcing every destination into one broad “stored injection” label.
For the next alert, draw one value's destinations before following every scanner arrow. Different destinations often explain why a broad warning contains both a real lead and a false positive. Use command review for process boundaries and frontend review for browser parsing.