Safeguard
Application Security•

CWE-184 Incomplete List of Disallowed Inputs: Why Denylists Fail

A denylist that misses one character or path form is a bypass waiting to happen. CWE-184 explained, with recent AI platform examples and fixes.

Safeguard Security Team
3 min read

CWE-184, Incomplete List of Disallowed Inputs, covers protections that rely on a list of forbidden inputs where the list is incomplete. If you block ten bad things and the attacker finds the eleventh, you have no protection against the eleventh.

It affects anyone who filters input by naming what is bad: shell characters, path patterns, file extensions, keywords.

How it happens

The author imagines the attacks they know about and lists them. Attackers do not limit themselves to that list. MITRE says attackers may find other malicious inputs the developer did not expect, which bypasses the intended protection.

The record includes recent entries, all chains where an incomplete denylist let a worse bug through:

  • CVE-2024-6091: an AI agent platform did not restrict pathnames containing internal "/./" sequences, leading to an incomplete denylist that did not prevent OS command injection.
  • CVE-2024-4315: an API for text generation with large language models left the "" Windows folder separator out of its denylist while trying to prevent local file inclusion through path traversal.
  • CVE-2024-44335: a filter checked for only some shell injection characters, enabling OS command injection.
  • CVE-2008-2309: a product used a denylist to identify dangerous content, and an attacker bypassed a warning.

A Python sketch of the pattern:

# Vulnerable: blocks a few characters, forgets others
BAD = [";", "|", "&"]
def ping(host):
    if any(c in host for c in BAD):
        raise ValueError
    os.system("ping -c 1 " + host)      # "$(id)" and newlines still work
# Fixed: allowlist the shape, and avoid the shell entirely
import re, subprocess
def ping(host):
    if not re.fullmatch(r"[A-Za-z0-9.-]{1,253}", host):
        raise ValueError
    subprocess.run(["ping", "-c", "1", host], check=True)

What an attacker gains

MITRE lists access control impact through bypass of a protection mechanism. The chains above show the downstream effect: command injection and file access.

How to find it

MITRE's black box note is practical: exploitation with commonly used manipulations might fail, but minor variations might succeed. So try variants, not just the textbook payload. Try other separators, other encodings, other operating system conventions. In review, treat every list of bad values as a finding to question.

How to fix it

The record lists no mitigations, so these are general steps.

  1. Replace the denylist with an allowlist describing what valid input looks like.
  2. Where possible, avoid the dangerous interpreter altogether. Pass arguments as arrays, use parameterised queries.
  3. If a denylist must remain, treat it as a second layer behind the allowlist, not the main control.
  4. Re-test it whenever the platform or operating system targets change, since the Windows separator example shows how a platform difference defeats a list.

Try it yourself

A scan of your own code can flag filters that match only a few bad characters.

Click through it at your own pace, or open it full screen.

Quick check

Why is an allowlist usually stronger than a denylist? An allowlist defines the small set of inputs you expect and rejects the rest. A denylist must predict every bad input, which nobody can do.

The Windows folder separator example shows what? A list written with one operating system in mind can miss the conventions of another.

Sources

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.