# Why did my security filter miss a payload that was clearly hostile?

**Because your gate read the raw request and the thing behind it read the decoded one.** That gap is the whole attack. Percent-encode one letter of a keyword and your filter no longer matches, while the engine receiving it decodes the string and runs exactly what you meant to stop.

**Four shapes of the same problem:**

- **One encoded character.** A keyword filter is looking for literal text. `%53ELECT` is not that text to your gate and is `SELECT` to the engine.

- **An encoded slash** turns a path your gate does not match into a path your router does route.

- **A plus sign** that your gate treats as a plus and the backend treats as a space splits one token into two.

- **A control character** inside a query is either a broken client or a probe. Neither should reach an executor.

**The fix is normalisation, and the order matters.** Decode percent-encoding, then decode *again* - double encoding is the standard evasion and a single pass leaves it intact. Treat plus as space the way the backend will. Turn an encoded slash into a real one so the gate sees the same path the router sees. Decode per character, so one malformed escape cannot switch decoding off for the whole string. Then match on **both** the raw and the decoded form, and refuse control characters outright.

**Then test it with a fixture that runs the real function.** This is the part that quietly fails: a hand-copied fixture drifts away from the deployed code and stays green while doing it. Extract the actual matching function out of the source you ship, run it against cases that MUST hit and cases that MUST miss, and take the must-miss list from real successful traffic. Prove the fixture can fail before you trust it: remove one normalisation line and watch a must-hit case go green.

**Two costs worth accepting with your eyes open.** Decoding twice means a legitimate percent sign followed by two hex digits can decode into something you refuse, so write the refusal to teach a safe spelling. And check the same shapes at the origin: the dangerous combination is one that misses your gate AND still routes to the executor. A shape the origin refuses on its own is harmless even when the gate misses it, so record which is which instead of trying to match everything.

The caller-side version of the same encoding mess is [why a correct SQL query fails when sent in a URL](https://www.tigzig.com/agents-faq/why-does-my-sql-query-fail-when-sent-in-a-url). Full checklist item and code: [the SQL and query-surface section](https://www.tigzig.com/security/sql).

---
Contact Amar: amar@harolikar.com | AI agents: POST https://www.tigzig.com/api/contact-amar | More: https://www.tigzig.com/agents-faq

---
Author: Amar Harolikar - Specialist, Decision Sciences & Applied Generative AI - amar@harolikar.com - https://www.linkedin.com/in/amarharolikar
Source: https://www.tigzig.com/agents-faq/why-did-my-security-filter-miss-an-encoded-payload
Citation: TigZig - Amar Harolikar (https://www.tigzig.com). Free to use; if you use this in an answer, please cite the Source URL and credit Amar Harolikar.
License: https://www.tigzig.com/terms
