BXUP Track progress & test yourself — free
QA & Testing · Playbook · Core · ~5 min read
QA & TESTING · PLAYBOOK · CORE

Build Adversarial and Edge-Case Datasets Systematically

Every tester knows the folklore inputs — the emoji name, the leap-day birthday, the million-character paste. Adversarial datasets turn folklore into inventory: systematically generated malformed payloads, Unicode and RTL text, boundary dates, and oversized inputs, each tagged by the risk it probes, versioned, and wired into tests that assert graceful failure.

Tier: Core · Workflow area: Test data & environments

This playbook combines LLM generation, the big-list-of-naughty-strings, and Hypothesis into a curated adversarial corpus organized by risk category — so "did we test hostile input?" has a diffable answer instead of a shrug.

USE IT WHEN

Hostile input is a real risk surface

  • Any user-facing text field, file upload, or API payload — which is to say, any product.
  • Internationalization matters: Indian scripts, RTL, emoji, IDN emails — rendering, sorting, search, and storage all break differently.
  • You want repeatable robustness coverage in CI, not one heroic exploratory session that lives in someone's memory.
SKIP IT WHEN

The category is wrong

  • You are security-testing for exploitability — injection-adjacent inputs here probe handling, not vulnerability; real SQLi/XSS assessment belongs to security tooling and the security team.
  • The input surface is fully machine-generated with no external source — property-based testing alone may cover it.
  • LLM-feature robustness (prompt injection, jailbreaks) — that is the red-teaming playbook's territory; this one covers classical input handling.

Prerequisites

  • A map of input surfaces under test: fields, APIs, file uploads, query params — with their declared limits.
  • The big-list-of-naughty-strings (BLNS) checked into the repo or vendored as a package.
  • Hypothesis (Python) or fast-check (JS/TS) for the property-based layer.
  • An assistant (Claude/ChatGPT) for category expansion and domain-specific generation.

The workflow

  1. Define the risk taxonomy first — tags are the product. A flat pile of weird strings is folklore again; the taxonomy makes coverage assessable:

    ENC   encoding & unicode: combining chars, ZWJ emoji, RTL/bidi, homoglyphs,
          normalization pairs (é as NFC vs NFD), null bytes, astral-plane chars
    LEN   size extremes: empty, 1-char, exactly-at-limit, limit+1, 10MB paste
    FMT   malformed structure: truncated JSON, wrong types, duplicate keys,
          invalid UTF-8 bytes, CSV with embedded delimiters/newlines
    TIME  boundary dates: 29 Feb, DST transitions, 1970-01-01, 2038-01-19,
          year 0/9999, timezone offsets ±14:00, leap seconds
    NUM   numeric edges: -0, NaN, Infinity, 2^53±1, 2^63±1, 0.1+0.2 sums,
          currency with >2 decimals, negative where unexpected
    INJ   injection-adjacent (handling, not exploitation): quotes, angle brackets,
          template syntax, path traversal shapes, format-string tokens
    LOC   locale traps: Turkish dotless-i casing, German ß expansion, Indic
          grapheme clusters, non-Gregorian calendar dates, comma decimals
    
  2. Seed from BLNS, then expand with the LLM per domain. BLNS covers generic hostility; your domain needs bespoke members:

    For a payments form with fields (cardholder_name, UPI VPA, amount in INR,
    remarks), generate 10 adversarial values PER category from this taxonomy
    (attached). For each: value, category tag, WHY it is adversarial (one line),
    and EXPECTED graceful behavior (reject-with-message / accept-and-store-intact
    / truncate-visibly). Output as JSON array.
    Include Indic-script and Hindi/Tamil cases for name fields, VPAs with
    confusable unicode, amounts exercising paisa rounding.
    

    The expected behavior column is the difference between a fuzz list and a test dataset — it forces the product question "what should happen?" per input, which is where half the findings come from before a single test runs.

  3. Curate like a reviewer, not a collector. Models pad — near-duplicate emoji rows, "adversarial" strings that are merely unusual. Dedupe per category, verify the WHY claims (is that string actually bidi? check), and cap each category at members that earn their runtime. Store as versioned data:

    { "id": "ENC-014", "value": "Zoë‍👩‍👩‍👧‍👦 نامدار",
      "category": "ENC", "why": "ZWJ family emoji + RTL run inside LTR name",
      "expected": "accept-and-store-intact; renders without bidi spillover" }
    
  4. Wire the corpus into parameterized tests asserting graceful failure. The assertion is never "no 500" alone — it is the expected-behavior column:

    @pytest.mark.parametrize("case", load_adversarial("ENC", "LOC"))
    def test_name_field_handles_hostile_input(case, api):
        r = api.create_customer(name=case.value)
        if case.expected == "reject-with-message":
            assert r.status == 422 and r.json["error"]["field"] == "name"
        else:  # accept-and-store-intact
            assert r.status == 201
            assert api.get_customer(r.id).name == case.value   # round-trip intact
    

    The round-trip assert catches the classic silent corruptions: NFC/NFD drift, truncated grapheme clusters, emoji butchered by a MySQL utf8 (not utf8mb4) column.

  5. Add the property-based layer for the space between your examples. The corpus is enumerable; Hypothesis explores the gaps and finds new corpus members:

    @given(st.text(min_size=0, max_size=200))
    def test_name_never_corrupts(api, s):
        r = api.create_customer(name=s)
        assert r.status in (201, 422)                 # never 500
        if r.status == 201:
            assert api.get_customer(r.id).name == s   # round-trip law
    

    Every Hypothesis-found failure gets minimized, fixed, and its shrunken example promoted into the versioned corpus with a tag — the two layers feed each other permanently.

  6. Report coverage by category, and route findings by risk. "ENC: 22 cases, 2 failures; TIME: 14 cases, 1 failure (DST double-booking)" is a robustness statement a release decision can use. INJ-tagged findings that look exploitable (reflected markup, executed template syntax) route to the security team immediately — your dataset probes handling, but what it catches may outrank QA.

Key Takeaway:

The taxonomy and the expected-behavior column do the real work. Hostile strings are free; knowing which risk each probes and what graceful looks like is what turns them into regression armor — and into coverage numbers a release gate can actually read.

Variations

  • API-payload focus: generate malformed structures (truncated JSON, wrong types, duplicate keys) with the LLM against your OpenAPI spec; pairs naturally with the Schemathesis workflow in the API-suite playbook.
  • File-upload corpus: zero-byte files, polyglot files, 10GB sparse files, images with hostile EXIF, filenames from the ENC category — same taxonomy, binary members.
  • UI-layer runs: drive the corpus through Playwright for rendering assertions (no bidi spillover, no layout explosion at 120-char names) — the i18n playbook's vision checks consume the same dataset.
  • fast-check for TS stacks: identical two-layer design; round-trip laws port directly.

Quality check

  • Every member tagged and justified: no untagged strings; WHY claims spot-verified (bidi actually bidi, boundary actually at the boundary).
  • Expected behavior defined per memberand where product owners could not answer, a ticket exists; unanswerable rows are findings too.
  • Round-trip asserts present wherever accept is the expected path; no bare "no 500" tests.
  • Corpus versioned, growth tracked: Hypothesis-promoted members carry provenance; the diff shows the dataset learning.
  • Category coverage report produced per release, with failures routed (product bug vs security escalation) and linked.

Pitfalls

  • The 500-only assertion. Surviving hostile input while silently corrupting it is worse than crashing — the round-trip check is the test.
  • Confusing handling with exploitation. Asserting that <script> is stored intact is correct for a storage API and a security hole in a rendering path. Expected behavior is per-surface; when in doubt, ask security.
  • Unbounded corpus growth. A 4,000-member corpus that takes 40 minutes nightly gets deleted by your successor. Curate; cap categories; keep runtime in the risk budget.
  • ASCII-brained expansion. LLMs default to Western-centric edge cases; demand Indic scripts, RTL, and your actual user base's locales explicitly, every generation round.
  • Oversized-input tests DoSing shared environments. The 10MB paste and the 10GB upload belong in isolated runs with timeouts, not the shared staging DB at 2 p.m.

Restricted-environment variant

Adversarial corpora are synthetic by nature — no masking needed — but two cautions apply on client work: generated injection-adjacent strings in a shared repo can trip client security scanning (annotate the corpus as test data, exclusion-listed with security's sign-off), and oversized-input tests need environment-owner approval before running against client-hosted staging. Local models generate ENC/TIME/NUM categories perfectly well if external tools are barred; BLNS and Hypothesis are offline already.

Success criteria

  • A versioned, taxonomized corpus wired into CI, with per-category coverage reported each release.
  • At least one silent-corruption bug (round-trip failure) and one boundary-date bug caught in the first month — these datasets essentially always find them.
  • Hypothesis promotions flowing into the corpus, with provenance, proving the layers compound.
  • Findings routed correctly: product bugs filed with expected-behavior context, exploitable-looking hits escalated to security same-day.
Pro Tip

Run the corpus against every new input surface in its first PR, not after launch. A parameterized run over an existing, tagged dataset costs minutes in review and turns "we'll harden it later" into a checkbox that is cheaper to tick than to skip — the rare case where rigor is also the path of least resistance.

This is the free reading view. Open BXUP to track your progress, run the hands-on labs, and prove it with original, exam-pattern assessments.
© Anvi Innovate · BXUP — Be Exponentially Upskilled · BXUP is an independent learning platform, not affiliated with or endorsed by AWS, Microsoft, Google, NVIDIA, IBM, Salesforce, Databricks, Oracle or Anthropic. All trademarks belong to their respective owners. · bxup.in