1. Insight
Insight
The problem this article addresses and why it matters.
Escape order is the silent injection bug
A string destined for nested contexts — a SQL value inside a JSON payload inside a URL parameter — needs escaping for each context in the right order. SQL first, then JSON, then URL. Get the order wrong and you produce broken output that may still look correct in a test fixture. Get the escaping wrong at any layer and you produce an injection vulnerability that surfaces in production traffic.
Most teams write this escape chain inline once per use case, copy-paste between similar use cases, and slowly accumulate per-call variation. The variation is invisible until something injects.
Why a multi-context chain
The tool in this article accepts a chain parameter that lists the escape contexts in order. The first context's output becomes the second context's input, and so on. The result is the input safely escaped for the full nested destination context. Each step of the chain is documented in the output so a developer or security reviewer can verify the order is correct.
What this article delivers
Per-context escaping walked end-to-end, the chain mode for nested destinations, and the cases where the order matters (SQL before JSON, not the other way around — escaping SQL syntax after wrapping in JSON would corrupt the JSON).
2. Intent
Intent
What you will be able to do after reading.
By the end of this article you will be able to:
- Escape strings for safe embedding in JSON, SQL, HTML, regex, shell, URI, CSV, or XML contexts
- Chain multiple escape contexts in order for nested destinations (a SQL value inside a JSON payload inside a URL parameter)
- Reverse the direction (unescape) for any of the supported contexts
- Identify the correct chain order for common nested-destination patterns
- Recognise the cases where the wrong order produces broken output or injection vulnerabilities
The Examples section walks through single-context escaping and a three-level chain for nested embedding.
3. Examples
Examples
Annotated code and worked scenarios.
Before / after: single-context escaping
Standard SQL escaping for a value with quotes:
stringEscaper({
text: "O'Brien's order",
format: 'sql',
direction: 'escape',
});
// result: "O''Brien''s order" (single quotes doubled)JSON escaping for a value with double quotes:
stringEscaper({
text: 'Path is "C:\\Users\\Alice"',
format: 'json',
direction: 'escape',
});
// result: 'Path is \\"C:\\\\Users\\\\Alice\\"'Shell escaping for a value with spaces and special characters:
stringEscaper({
text: 'rm -rf $HOME/important',
format: 'shell',
direction: 'escape',
});
// result: "'rm -rf \$HOME/important'" (wrapped in single quotes, $ escaped)Before / after: multi-context chain
The use case: a SQL query value that lands inside a JSON request body that lands inside a URL parameter.
stringEscaper({
text: "O'Brien",
format: 'sql',
direction: 'escape',
chain: ['sql', 'json', 'uri'],
});
// chainSteps: [
// { step: 1, format: 'sql', input: "O'Brien", output: "O''Brien" },
// { step: 2, format: 'json', input: "O''Brien", output: "O''Brien" }, // single quotes don't need JSON escape
// { step: 3, format: 'uri', input: "O''Brien", output: "O%27%27Brien" },
// ]
// result: "O%27%27Brien"Each step's input is the previous step's output. The final result is the input safely escaped for the full nested destination. The chainSteps array is visible in the output so a security reviewer can verify the transformation is correct without re-running the chain by hand.
Before / after: getting the order wrong
What happens with the wrong order:
// WRONG order: URI first, then SQL
stringEscaper({
text: "O'Brien",
chain: ['uri', 'sql', 'json'],
});
// chainSteps: [
// { step: 1, format: 'uri', input: "O'Brien", output: "O%27Brien" },
// { step: 2, format: 'sql', input: "O%27Brien", output: "O%27Brien" }, // SQL escape sees no apostrophe to double
// { step: 3, format: 'json', input: "O%27Brien", output: "O%27Brien" },
// ]
// result: "O%27Brien"The wrong order produces a string that's URI-encoded but not SQL-escaped. When the receiving SQL system decodes the URI, the apostrophe reappears un-escaped — an injection vulnerability. The right order (SQL first) escapes the SQL syntax BEFORE wrapping it in URI encoding so the SQL-significant character never appears in the final URI in its dangerous form.
Before / after: unescape direction
stringEscaper({
text: "O%27%27Brien",
format: 'uri',
direction: 'unescape',
});
// result: "O''Brien"Useful for reading values stored in escaped form. Chain unescape isn't supported — the tool requires the consumer to know the order they were applied in. Run unescape per layer.
When humans use this
A developer constructing a nested payload (SQL in JSON in URL — common in audit-log query APIs) uses chain mode to produce the correctly-escaped result in one call. The chainSteps array is reviewed in code review to confirm the order is correct.
When agents use this
Two patterns:
- Query construction. An agent building a SQL query parameterised through a JSON API uses chain mode to escape user input correctly for the full path. The chain documentation in the output is auditable evidence that the escape was applied.
- Test fixture generation. An agent generating test fixtures with adversarial inputs (apostrophes, quotes, backticks) uses single-context mode to produce values that exercise each escape layer independently.
Edge cases
Unicode and surrogates
The escaper handles BMP and supplementary characters correctly. JSON escape uses \uXXXX for non-ASCII; URI escape uses percent-encoded UTF-8 bytes. Shell escape preserves Unicode literally because most shells handle UTF-8 transparently.
Empty chain
A chain with no entries returns the input unchanged. A chain with one entry is equivalent to single-context mode. Useful for callers that build the chain dynamically and may end up with zero or one entries.
Round-trip stability
Escape then unescape produces the original input for all supported contexts. Unescape then escape is stable for context-canonical forms; non-canonical inputs (%2f vs %2F in URI) get normalised to canonical on round-trip.
4. Documentation
Documentation
Reference signatures, edge cases, and lookup tables.
Input parameters
Field | Type | Required | Default | Description |
|---|---|---|---|---|
|
| ✓ | — | The string to escape or unescape |
|
| conditional | — | Single-context format (when |
|
| ✓ | — | Direction |
|
| ✗ | — | Multi-context chain — list contexts in application order |
Output shape
{
result: string;
chainSteps?: Array<{
step: number;
format: string;
input: string;
output: string;
}>;
}Per-context escape tables (escape direction)
Char | json | sql | html | regex | shell | uri |
|---|---|---|---|---|---|---|
|
|
|
|
| (wrap+escape) |
|
|
|
|
|
| (wrap+escape) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Chain order recommendations
Destination context | Chain |
|---|---|
SQL in JSON body |
|
SQL in JSON in URL param |
|
HTML attribute value containing JSON |
|
Shell command in JSON config |
|
Order matters: escape for the innermost context first, then each outer context. The wrong order leaves an injection vector at the innermost layer.
Error codes
Code | When it fires | Recovery |
|---|---|---|
|
| Provide a non-empty string |
|
| Use one of the eight documented formats |
When NOT to use this tool
For HTML sanitisation (removing dangerous markup), use a sanitiser like DOMPurify. The escaper escapes everything to text; the sanitiser preserves whitelisted markup.
For SQL parameterisation in production code, use the database driver's parameterised query API ($1, $2 in pg, ? in mysql2). String escaping is a fallback when parameterisation isn't available — typically for dynamic table/column names. For value parameters, the driver's parameterisation is strictly safer.
Performance notes
Typical execution: under 2ms. Chain mode adds <1ms per chain entry. Deterministic — same input + same chain produce byte-identical output, so REST responses are Edge-Cache eligible.