skip to content

How do you restate the mitigation note "validate the uploaded XML" as a testable requirement?

level: seniorimportance: must knowfreq 52%

answer

  1. "validate" is not falsifiable
  2. component, condition, observable outcome
  3. check the catalog before writing your own prose
  4. give the weakness a shared number
  5. CWE-611 for external entity resolution

basics

~20 s

A verification requirement names a component, a condition and an observable outcome, so one input can make it fail: the ingest service parses XML with external entity resolution disabled and rejects a DOCTYPE with a 400. Tag it CWE-611.

solid answer

~50 s

"Validate the uploaded XML" names no behaviour, so no test can fail it. I restate it with a component, a condition and an observable outcome: `Verify that the invoice-ingest service parses uploaded XML with DTD processing and external entity resolution disabled, rejects any document containing a DOCTYPE declaration with a 400, and records the rejection in the audit log.` Before writing prose I look for an ASVS requirement that already covers parsing untrusted structured data, because its wording is already testable and carries a level and a CWE mapping. Then I tag the threat CWE-611, improper restriction of XML external entity reference, so the modeled information-disclosure threat, the requirement and any review rule share one name. The test is falsifiability: if I cannot state the input that makes it fail, it is still a wish.

go deeper

for a junior

Recognise that "validate the input" is advice, not a requirement, and that a requirement has to name what a tester should send and what they should see back. Learn the habit of deleting the word "properly" and seeing what is left.

for a middle

Explain the three parts — component, condition, observable outcome — and show where the enforcement point and the default belong in the sentence. Be able to say why a disabled parser feature verifies more cleanly than a filter.

for a senior

Show the whole hop from a modeled threat to a sentence someone can fail, including looking for existing catalog wording first and attaching a weakness identifier so duplicates across services collapse into one fix.

for a principal

Own the standard itself: how requirements get phrased consistently across teams, when a recurring weakness becomes a platform default rather than N per-service requirements, and how you stop the requirement set from becoming unverifiable boilerplate.

### Why "validate the uploaded XML" is not yet a requirement The note is a **mitigation intention**. It has no subject you can point at, no condition, and no outcome you could observe, so there is no input that makes it fail. Anything unfalsifiable is untestable, and anything untestable will be marked done by whoever is in a hurry. Words that signal this failure: *properly, securely, appropriately, robustly, as needed, where applicable, harden, sanitise*. If the sentence survives having the word "properly" deleted with no loss of meaning, the word was doing all the work. Keep the four artifacts distinct while you rewrite: | Artifact | What it says | | --- | --- | | Threat | An anonymous uploader submits a crafted document and reads a file from the server | | Weakness | The parser resolves external entities in untrusted input | | Control | Parse with entity resolution and DTD processing off | | Verification requirement | A testable sentence proving the control is present in this build | ### The shape of a verification requirement Three parts, and a published catalog will have all three: 1. **Component** — which service or entry point. Not "the system". 2. **Condition** — the circumstance under which the behaviour must hold, including the adversarial one. 3. **Observable outcome** — what an observer sees. A rejection, a status code, a log record, an absent side effect. Applied to the upload: > Verify that the invoice-ingest service parses uploaded XML with DTD processing and external > entity resolution disabled, rejects any document containing a DOCTYPE declaration with a 400 > response, and writes the rejection to the audit log with the submitting IP and document hash. Now a tester knows exactly what to send and exactly what to look for, and a reviewer can read the parser configuration and say yes or no. Notice what the rewrite forced out into the open that the original hid: the *enforcement point* (server side, in the ingest service, not in a browser-side check), the *default* (reject rather than best-effort clean-up), and the *evidence* (a log line, so an attempt is visible even though it failed). ### Check the catalog before writing prose ASVS already contains requirements for parsing untrusted structured data, and using its wording has three advantages over inventing your own. The phrasing is already testable, so you inherit the discipline. The requirement carries a **verification level**, which tells you whether meeting it is part of your baseline or an above-baseline commitment. And it usually carries a CWE mapping, so the identifier work is done. Write your own only when nothing matches — and then write it in the same shape, so a reader cannot tell which requirements came from the catalog and which from you. ### What the CWE tag adds Tag this one **CWE-611, Improper Restriction of XML External Entity Reference**. That number is not bureaucracy; it is the join key. Three separate teams — the person who found the threat, the person who wrote the verification requirement, the person who later writes a detection or code-review rule — now refer to the same weakness class without having read each other's documents. And when the same weakness appears in the export service and the partner feed, the backlog shows one weakness with three instances rather than three unrelated tickets, which changes the fix from three patches to one hardened parser factory that every service uses. Two cautions on the tagging. First, tag the **weakness**, not the attack story: a single CWE can be reached through many routes, and one route can involve several weaknesses. Second, a CWE is a name, not a rating: it carries no severity, no deadline and no evidence that anything was fixed. ### Where this sits in the STRIDE flow The modeled threat here is **information disclosure** — the external entity makes the parser fetch a local file and return its contents in an error or a response field — which violates **confidentiality**. The asset is not customer data at all: it is internal file contents and whatever credentials the process can read from disk. Naming the asset that way matters, because it is what justifies a rejection-by-default requirement rather than a filtering one, and it explains why "block dangerous entities" is a weaker requirement than "resolve no entities at all": a denylist has to be right every time, a disabled feature has to be right once. ### A senior habit After rewriting, read the requirement back and ask: *what input makes this fail, and can I write it in one line?* For the version above, the answer is immediate — a document with a DOCTYPE and an external entity referencing a local path must come back 400 with a log entry. If you cannot answer that question, you have written a wish in the register of a requirement, and the difference will only surface at the point where someone claims the mitigation is in place and nobody can check.

  • What if no ASVS requirement matches the threat you modeled?
    Write your own in the same shape — verify that <component> does <observable thing> under <condition> — and attach a CWE if a weakness class fits. Mark it as project-specific so a later reader knows it did not come from the catalog. The value of the catalog is its discipline of phrasing, which you can apply without it.
  • Your requirement names one service. When should it become a portfolio-wide standard instead?
    When the same weakness class recurs across services, which the CWE tag makes visible. Then the fix is one hardened parser configuration exposed as a shared default, and the requirement moves to the platform: verify that services parse untrusted documents through the shared factory. Per-service requirements should be reserved for context you genuinely cannot centralise.
  • Why is "reject documents containing a DOCTYPE" a stronger requirement than "filter dangerous entities"?
    A denylist has to be right on every input an attacker can invent, and encoding tricks and nested declarations keep expanding that surface. Disabling the feature has to be right once, in configuration, and it is trivially verifiable — you either resolve external entities or you do not. Prefer requirements that name a disabled capability over ones that name a filter.
  • Which STRIDE category does this threat sit in, and which property does it violate?
    Information disclosure, violating confidentiality: the parser is coaxed into reading a local file and returning its contents in a response or error. Naming the asset as internal file contents and process-readable credentials, rather than customer data, is what justifies a reject-by-default requirement instead of a filtering one.

A mitigation note is a promise made to yourself; a verification requirement is a receipt someone else can hold you to.

saying these in an interview costs you the question

  • Leaves words like properly, securely or sanitise in the requirement
  • Writes the requirement about the system rather than a named component
  • Treats a CWE identifier as a severity rating or a deadline
  • Puts the validation in a client-side check and calls it verified
  • Cannot name an input that would make the requirement fail

context