Safeguard
Developer Tools•

How do you write one security test for many attacker inputs in pytest?

Parametrize the test so each hostile input runs as its own case. Failures name the exact input, and adding a new payload is a one line change.

Safeguard Security Team
4 min read

Use @pytest.mark.parametrize to run one test body against a list of hostile inputs. Each input becomes its own case with its own name, so a failure points at the exact payload, and adding a new attack string takes one line instead of a new test function.

How parametrize works

The pytest documentation says the marker takes argument names and a list of value sets, and the test runs once per set. Each case gets an automatic ID built from its values. Stacked decorators produce all combinations, so two decorators with two values each run four cases. The marker can sit on a function, a class or a whole module.

For security tests this gives a clean pattern: the behavior is written once, the data lives in a table.

A pattern for input validation

Suppose an endpoint should reject certain values for a name field. Write the rule once:

import pytest

HOSTILE = [
    "' OR '1'='1",
    "../../etc/passwd",
    "A" * 10000,
    "",
    "name\x00.txt",
]

@pytest.mark.parametrize("value", HOSTILE)
def test_name_field_rejects_hostile_input(client, value):
    response = client.post("/profile", json={"name": value})
    assert response.status_code in (400, 422)

Each string runs separately. If the long string fails but the others pass, the report shows which one.

Tips that save time

  1. Name cases clearly. A readable ID beats a wall of escaped characters in a failure message.
  2. Group by bug class. One list for injection strings, one for path tricks, one for size limits.
  3. Keep the expected outcome with the data when it differs per case, using tuples.
  4. Keep the lists in a separate module so security and QA can both review and extend them.
  5. Link each unusual value to the incident or ticket that introduced it, in a comment.

Watch the shared state trap

The docs warn that parameter values are passed as is, not copied. If a test mutates a list or dictionary parameter, later cases see the change. A test that edits a shared payload can quietly weaken every case that follows. Build fresh objects inside the test, or use immutable values like tuples and strings.

Combining with other markers

NeedApproach
One input still works, fix scheduledWrap it with pytest.param and an xfail mark
Cover role and input togetherStack two decorators for all combinations
Same rule on every endpointParametrize over a list of paths
Only on some platformsAdd a skip condition with a reason

The docs show that marking one case with xfail reports it as xfailed while the rest pass, which keeps the known gap visible without breaking the suite.

What this does not replace

A fixed list only checks what you thought of. Pair it with a fuzzer or property based test that generates inputs you did not think of, and feed anything those tools find back into the list as a permanent case. The list grows from real failures, which is what makes it valuable over time.

Also check the response body and side effects, not only the status code. A 400 that still wrote a row to the database is a failure.

Try it yourself

Click through it at your own pace, or open it full screen. See how one scan exercises many endpoints and parameters, then compare with your own hostile input table.

Safeguard's free scan covers dependency and SBOM findings. Input tests for your own endpoints stay in your suite.

Quick check

Does each parametrized case show up separately?

Yes. Each value set runs as its own test with its own ID, so failures name the input.

Can parameters leak between cases?

Yes, because values are not copied. Avoid mutating shared lists or dictionaries inside the test.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.