Most software security bugs are not caused by mysterious attackers doing mysterious things. They are caused by ordinary data arriving in a place where the software treats it as instructions. An unsanitised output sink is one of those places: a point where data is written into a page, command, document, log, message, or other output without the right protection for the meaning of that place.

Start with data, instructions, and sinks

A program constantly moves data from one place to another. It reads a name from a form, retrieves a comment from a database, builds a page, writes a log line, or sends a message to another system. In security work, the place data comes from is often called a source, and the place it is finally used is called a sink.

A sink is not automatically dangerous. Printing a user name on a web page is normal. Writing a search term into a log is normal. The risk appears when the sink is also interpreted by something else, such as a browser, command shell, database engine, template engine, or document reader. If the data can change how that interpreter understands the output, data has crossed the line into instructions.

An unsanitised output sink is therefore a sink that receives data without the handling needed to keep it as data. The handling might be encoding, escaping, sanitisation, validation, or use of a safer application programming interface, depending on the context.

Data crosses into instructions when a sink is interpreted by something that can run it. Source to Sink Source Sink Interpreter Runs as code
Data crosses into instructions when a sink is interpreted by something that can run it.

Why the browser example matters

The easiest example is a web page. A browser does not see a page as plain text. It parses HyperText Markup Language, or HTML, reads attributes, follows Uniform Resource Locators, or URLs, applies Cascading Style Sheets, or CSS, and runs JavaScript. Each of those parts has its own grammar.

Suppose a site takes a display name and inserts it directly into an HTML page. If the name is Alice, nothing surprising happens. If the name contains characters that the browser treats as markup, the browser may stop seeing a name and start seeing a tag or script. That is the basic shape of cross-site scripting, or XSS, where attacker-controlled content runs in another user’s browser.

The same principle exists outside browsers. A report generator can treat data as spreadsheet formulas. A shell command can treat data as flags or commands. A log viewer can treat data as markup. The sink changes, but the failure is the same: the program let data arrive in a place where it could be reinterpreted.

⚠️
Core risk. The danger is not that a string contains strange characters; it is that the receiving context gives those characters power.

Context decides the correct defence

There is no single safe version of a value. The safe form depends on where the value will be placed. Text inside an HTML paragraph, text inside an HTML attribute, a value inside a JavaScript string, a path segment inside a URL, and a value inside CSS all need different handling.

This is why generic cleaning functions so often fail. A function that removes one dangerous-looking pattern may still leave a value unsafe in another context. It may also damage legitimate data. Security improves when code makes the context explicit and uses the protection designed for that context.

A useful mental test is simple: ask what language the sink speaks. If the sink speaks HTML, use HTML-aware output encoding. If it speaks JavaScript, use JavaScript string encoding or avoid placing dynamic data in script at all. If it speaks a command-line language, avoid string-built commands and pass arguments through a safer interface.

  • HTML text needs characters such as less-than and ampersand represented so the browser treats them as text.
  • HTML attributes need attribute encoding and careful treatment of quotation marks and event-handler attributes.
  • JavaScript strings need JavaScript-safe encoding, and dynamic code execution should be avoided.
  • URLs need component-aware encoding because a whole URL and one query parameter are not the same context.
  • Rich user-authored HTML needs a strict allowlist sanitizer because encoding everything would remove the intended formatting.
The safe form depends on which language the sink speaks. What Language Sink speaks? HTML JavaScript Command HTML encodetag-safe JS encodestring-safe Argument listno string cmd
The safe form depends on which language the sink speaks.

People often use the word sanitising to mean any attempt to make data safe. That loose usage is understandable, but it hides important differences. Validation checks whether input is acceptable. Sanitisation changes input by removing or allowing selected parts. Encoding represents characters in a form that the output interpreter treats as data. Escaping is a kind of encoding used to neutralise special meaning in a particular syntax.

For many output sinks, encoding at the point of output is the safest routine answer. It preserves the original data and adapts it to the exact context. For example, a person’s name should not be permanently changed in storage just because one page needs an HTML-safe representation of it.

True sanitisation is needed when the product intentionally accepts a language-like input, such as user-authored rich HTML. In that case the goal is not to turn every tag into text. The goal is to allow a small, well-understood subset and remove everything else that can execute code, change navigation, hide content, or abuse browser behaviour.

Validation, sanitisation, encoding, and escaping are related but not the same. Four Defences Validationcheck input Sanitisationremove parts Encodingdata at output Escapingneutralise syntax
Validation, sanitisation, encoding, and escaping are related but not the same.

How the weakness shows up in real code

An unsanitised output sink often appears as string construction. A program builds a fragment of HTML, JavaScript, Structured Query Language, or SQL, a shell command, or a document by joining trusted syntax with untrusted values. The more invisible the joining is, the harder the weakness is to see during review.

Template systems can reduce the risk, but only when their safe defaults are left on and their escape hatches are treated as dangerous. Many systems provide features with names such as raw output, unescaped output, safe HTML, or trusted markup. Those features are sometimes necessary, but every use should raise the question: who proved this value is already safe for this exact context?

Stored data deserves the same suspicion as request data. A comment saved yesterday can attack a moderator today. A supplier name imported from a partner can break an internal dashboard. A log entry written by one service can affect an analyst viewing it in another service. The source may be old, indirect, or internal, but the sink still interprets it now.

  • Look for direct string concatenation near generated pages, commands, documents, and messages.
  • Review template escape hatches and any helper named raw, safe, trusted, or unescaped.
  • Trace data from stored records, partner feeds, queues, and logs, not only from live user input.
  • Check whether the protection matches the final sink, not an earlier intermediate format.
⚠️
Do not trust storage. A database preserves data; it does not prove that data is safe for every future output context.

Controls that survive maintenance

The best control is to make the safe path the easy path. Frameworks, templating engines, and shared libraries should encode by default for common contexts. Developers should have to make a deliberate, reviewable choice to produce raw output.

Separate data from instructions where possible. Use parameterised database queries instead of building SQL strings. Pass command arguments as an argument list instead of building one command string. Use document builders and browser document object model methods that create text nodes rather than parsing strings as markup. The document object model, or DOM, is the browser’s structured representation of a page.

Layered controls also help. Content Security Policy, or CSP, can limit what scripts a browser may run. It is not a substitute for correct output handling, but it can reduce the blast radius of a missed sink. Logging, code review, automated scanning, and tests each catch different failures.

✅
Best default. Prefer designs where developers must opt out of safety, not remember to opt in.
Encode by default, separate data from instructions, and add a policy to limit blast radius. Controls That Last Encode by defaultsafe path is easy Parameterisedata not string Content policylimit blast radius
Encode by default, separate data from instructions, and add a policy to limit blast radius.

The decision lens for leaders and practitioners

For a leader, the question is not whether every developer knows every encoding rule by memory. The practical question is whether the organisation’s platforms make the common case safe and the risky case visible. This affects framework selection, design standards, backlog priority, training, and review policy.

For a practitioner, the question is where untrusted data is allowed to become syntax. If a feature requires rich formatting, file generation, embedded scripts, or integration with another interpreter, it deserves explicit design attention before implementation. If a team is migrating front-end frameworks or template engines, it should treat changes in auto-escaping behaviour as a security-relevant decision.

Risk also depends on who can supply the data and who will view the output. A public comment shown to other users is high concern. A partner feed shown to staff may still be high concern. An administrator-only field can still matter if it reaches a page with privileged browser access.

  • Choose platforms with context-aware output encoding by default.
  • Require review for raw output, trusted markup, dynamic script construction, and command construction.
  • Prioritise fixes where attacker-controlled data reaches browsers used by other people or privileged staff.
  • Ask teams to document the intended output context for reusable rendering helpers.
  • Track recurring sink findings as design problems, not only as individual coding mistakes.

Testing and proving control

Testing an output sink is not only about trying a famous payload and seeing whether an alert box appears. A good test first identifies the context, then uses input that would be meaningful in that context if it were not encoded or otherwise controlled. The expected result is that the output displays or carries the data without changing the structure or behaviour of the surrounding document, command, or message.

Static application security testing, or SAST, can help by tracing data from sources to sinks. Dynamic application security testing, or DAST, can help by exercising a running application and observing behaviour. Both are useful, and both can be wrong. Human review is still needed to decide whether the reported sink is reachable, whether the data is trusted, and whether the protection is correct for the final context.

The most convincing proof is boring: a small set of safe construction patterns used consistently, tests that fail when encoding is removed, and a short list of exceptional raw-output sites that have named owners and clear justification.

📌
Good evidence. A controlled sink is shown by context-aware construction and repeatable tests, not by the absence of one successful attack string.

Key takeaways

  • An unsanitised output sink lets data reach an interpreting context without the handling needed to keep it as data.
  • The right defence depends on the output context; there is no universal safe string.
  • Encoding at output is usually preferable to changing stored data, while true sanitisation is reserved for allowed rich content.
  • Safe defaults, typed interfaces, and rare reviewed escape hatches prevent more defects than memory-based rules.
  • Stored, internal, and partner-supplied data can still become dangerous when later rendered or interpreted.
  • Leaders should treat repeated sink findings as platform and design issues, not just developer mistakes.

Glossary

Source
A place where data enters a program or trust boundary, such as a form, file, database, queue, or partner feed.
Sink
A place where a program uses or outputs data, especially one where another system may interpret that data.
Sanitisation
The process of changing input by removing or allowing selected content so it fits a defined safety policy.
Encoding
The process of representing characters so an output interpreter treats them as data rather than syntax.
Context
The specific language or position where data is placed, such as HTML text, an attribute, a URL part, or a script string.
Cross-site scripting (XSS)
A web vulnerability where attacker-controlled content runs as script in another user’s browser.
Document object model (DOM)
The browser’s structured representation of a web page, which programs can read and modify.
Content Security Policy (CSP)
A browser security policy that restricts where scripts and other resources may come from and how they may run.
Static application security testing (SAST)
Automated analysis that examines code or compiled artifacts to find possible security weaknesses without running the application.