Metadata-Version: 2.4
Name: flask-antifuzz
Version: 0.1.0
Summary: Flask extension that masks 404 responses to reduce directory fuzzing signal quality.
Author: AntiFuzz contributors
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/sparklair/AntiFuzz
Project-URL: Repository, https://github.com/sparklair/AntiFuzz
Project-URL: Issues, https://github.com/sparklair/AntiFuzz/issues
Keywords: flask,security,fuzzing,directory-fuzzing,404
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Web Environment
Classifier: Framework :: Flask
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
Classifier: Topic :: Security
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: Flask>=2.3
Provides-Extra: test
Requires-Dist: pytest>=8; extra == "test"
Dynamic: license-file

# Flask AntiFuzz

Flask AntiFuzz is a simple Flask extension that reduces the usefulness of directory fuzzing signals by masking 404 Not Found responses as normal-looking HTML responses with variable hidden padding.

This extension can be considered a protection against directory fuzzing aimed at identifying sensitive and insecure endpoints. It will also help mitigate certain types of credential stuffing attacks, when an attacker targets noticeable variations in the server response size and other scenarios where the size of the server response is important.

From the attacker's side (example):
<img width="1257" height="1013" alt="изображение" src="https://github.com/user-attachments/assets/4e194858-9834-463f-b8fc-396a6162f8be" />

## Installation

```bash
pip install flask-antifuzz
```

## Usage

```python
from flask import Flask
from flask_antifuzz import Antifuzz

app = Flask(__name__)
antifuzz = Antifuzz(app)
```

The extension also supports the app factory pattern:

```python
from flask import Flask
from flask_antifuzz import Antifuzz

antifuzz = Antifuzz()


def create_app():
    app = Flask(__name__)
    antifuzz.init_app(app)
    return app
```

## Demo Applications

The repository includes two small Flask applications for comparison:

| Path | Purpose |
| --- | --- |
| `app/` | Protected demo application with `Antifuzz(app)` enabled. |
| `fuzzed/app/` | Intentionally unprotected baseline application for directory-fuzzing comparisons. |

The `fuzzed/app/` service is kept in git on purpose. It is not included in the
published wheel.

## Configuration

Set values on `app.config` before initializing the extension.

| Key | Default | Description |
| --- | --- | --- |
| `ANTIFUZZ_ENABLED` | `True` | Enables or disables the 404 masking handler. |
| `ANTIFUZZ_STATUS_CODE` | `200` | Status code returned for missing paths while enabled. |
| `ANTIFUZZ_MIN_RESPONSE_BYTES` | `None` | Manual minimum target response size. Must be set with `ANTIFUZZ_MAX_RESPONSE_BYTES`. |
| `ANTIFUZZ_MAX_RESPONSE_BYTES` | `None` | Manual maximum target response size. Must be set with `ANTIFUZZ_MIN_RESPONSE_BYTES`. |
| `ANTIFUZZ_AUTO_SIZE_ENABLED` | `True` | Automatically derives response size bounds from template/static HTML-like files. |
| `ANTIFUZZ_FALLBACK_MIN_RESPONSE_BYTES` | `6144` | Minimum target response size when auto sizing finds no files. |
| `ANTIFUZZ_FALLBACK_MAX_RESPONSE_BYTES` | `163840` | Maximum target response size when auto sizing finds no files. |
| `ANTIFUZZ_SIZE_MARGIN_RATIO` | `0.25` | Extra proportional size margin added around discovered file sizes. |
| `ANTIFUZZ_SIZE_MIN_MARGIN_BYTES` | `512` | Minimum byte margin added around discovered file sizes. |
| `ANTIFUZZ_SIZE_FILE_EXTENSIONS` | `(".html", ".htm", ".jinja", ".jinja2", ".j2")` | File extensions used for auto sizing. |
| `ANTIFUZZ_PAD_ALL_HTML_RESPONSES` | `True` | Adds the same hidden padding shape to normal HTML responses. |
| `ANTIFUZZ_MIN_PADDING_BYTES` | `96` | Minimum hidden padding added to each padded HTML response. |
| `ANTIFUZZ_RAND_404_TITLE` | `False` | Randomizes the `<title>` value on masked 404 HTML responses. |
| `ANTIFUZZ_404_TITLE_MIN_LENGTH` | `5` | Minimum randomized 404 title length. |
| `ANTIFUZZ_404_TITLE_MAX_LENGTH` | `10` | Maximum randomized 404 title length. |
| `ANTIFUZZ_RENDERER` | `None` | Optional callable for an explicit extension-managed 404 response. |

## Response Sizing

You do not need to configure response sizes for a typical Flask app. Manual size
settings default to `None`, so AntiFuzz starts in auto-sizing mode.

By default, AntiFuzz scans the Flask application's configured `template_folder`
and `static_folder` during initialization. It looks for HTML/Jinja-like files,
measures their source file sizes, then adds a safety margin to build the random
response-size range used for masked `404` responses.

The configured size range is a final response-size target, not the amount of
padding to add. AntiFuzz subtracts the current HTML body size before adding
padding. If the response is already above the maximum, or if there is not enough
remaining room for the hidden block, it leaves the body unchanged instead of
growing the response past the configured maximum.

Manual sizing takes priority over auto sizing:

```python
app.config["ANTIFUZZ_MIN_RESPONSE_BYTES"] = 3000
app.config["ANTIFUZZ_MAX_RESPONSE_BYTES"] = 9000
Antifuzz(app)
```

Use manual sizing for highly dynamic pages where rendered HTML is much larger
than the source Jinja template.

The fallback range is based on HTTP Archive's 2025 Web Almanac HTML response
size distribution: common HTML responses cluster around a 33-35 KB median, with
the 90th percentile around 148-155 KB.

## Padding Placement

AntiFuzz pads all non-streamed `text/html` responses by default, not only `404`
responses. This keeps the defensive page from being identifiable by the mere
presence of hidden padding.

The padding is inserted immediately before `</body>`. If a response has no
closing body tag, the padding is appended to the end of the response. The block
is a plain hidden `div` with text-like random payload and does not include any
project-specific signatures.

## 404 Handling

AntiFuzz does not replace your application's 404 page. Flask or your own
`@app.errorhandler(404)` still creates the response body. AntiFuzz then changes
the response status from `404` to `ANTIFUZZ_STATUS_CODE` and applies the same
HTML padding layer used for normal pages.

If `ANTIFUZZ_RAND_404_TITLE` is enabled, AntiFuzz also replaces the 404 page's
`<title>` value with a random ASCII title. If the page has a `<head>` but no
`<title>`, AntiFuzz inserts one.

## Custom Responses

Prefer Flask's normal error-handler API for application-owned 404 pages:

```python
@app.errorhandler(404)
def not_found(error):
    return render_template("error404.html"), 404
```

AntiFuzz will keep that body, change the status code, and apply padding. Use
`ANTIFUZZ_RENDERER` only when you explicitly want the extension to own 404
rendering:

```python
from flask import render_template


def antifuzz_renderer(error):
    return render_template("error404.html"), 200


app.config["ANTIFUZZ_RENDERER"] = antifuzz_renderer
Antifuzz(app)
```

## Development and testing

```bash
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -U pip
python -m pip install -e ".[test]"
python -m pytest
```

Build and validate package artifacts:

```bash
python -m pip install build twine
python -m build
python -m twine check dist/*
```

Run the demo applications:

```bash
flask --app app/main.py run --port 5000
flask --app fuzzed/app/main.py run --port 5001
```
