obfus.link
Encoders

Multi-context escape chains for SQL-in-JSON-in-URL nested payloads

Escape strings for safe embedding in JSON, SQL, HTML, regex, shell, URI, CSV, or XML. Chain multiple contexts in order for nested destinations — the only way to safely escape a SQL value inside a JSON payload inside a URL parameter.

The String Escaper escapes strings for one of eight embedding contexts (JSON, SQL, HTML, regex, shell, URI, CSV, XML) or chains multiple contexts in order for nested destinations. The chain output documents each transformation step — useful for security review of the escape order, since wrong-order chaining produces injection vulnerabilities that look correct in fixtures.

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

text

string

The string to escape or unescape

format

'json' | 'sql' | 'html' | 'regex' | 'shell' | 'uri' | 'csv' | 'xml'

conditional

Single-context format (when chain is not used)

direction

'escape' | 'unescape'

Direction

chain

string[]

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

'

'

''

&#39;

\\'

(wrap+escape)

%27

"

\\"

"

&quot;

\\"

(wrap+escape)

%22

\\

\\\\

\\\\

\\

\\\\

\\\\

%5C

<

<

<

&lt;

<

<

%3C

>

>

>

&gt;

>

>

%3E

&

&

&

&amp;

&

&

%26

$

$

$

$

\\$

\\$

%24

Chain order recommendations

Destination context

Chain

SQL in JSON body

['sql', 'json']

SQL in JSON in URL param

['sql', 'json', 'uri']

HTML attribute value containing JSON

['json', 'html']

Shell command in JSON config

['shell', 'json']

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

INPUT_EMPTY

text empty

Provide a non-empty string

INPUT_INVALID_TYPE

format or chain entry outside supported set

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.

Try it now

String Escaper

Escape strings for JSON, SQL, HTML, regex, shell, URI, CSV, XML

FAQ

Frequently asked questions

Why does the chain order matter?

Apply the innermost context first. SQL in JSON in URL means SQL escape first, then JSON escape on the result, then URI escape on that. The wrong order (URI first) leaves the SQL value un-escaped when the receiver decodes the URI — an injection vulnerability that passes test fixtures because the URI decode is "correct."

Is this a substitute for parameterised queries?

No. Always prefer the database driver's parameterised query API ($1, $2 in pg, ? in mysql2). String escaping is a fallback for dynamic table or column names where parameterisation isn't available. For value parameters, the driver's API is strictly safer.

Does it handle Unicode?

Yes. JSON uses \uXXXX for non-ASCII, URI uses percent-encoded UTF-8 bytes, shell preserves Unicode literally (most shells handle it transparently). Surrogate pairs are handled correctly across all contexts.

Can I unescape a chain?

No, not in one call. The unescape direction works on single contexts only — call unescape per layer in reverse order. This is intentional: chained unescape introduces ambiguity that's safer to require the caller to make explicit.