Every file claims to be something. Byte Bouncer asks for ID.
An extension is a label the user (or an attacker) types. A magic number is
byte-level evidence baked into the file's header. Byte Bouncer reads the header,
identifies what the file actually is, and flags any file whose extension and
content disagree — with special attention to the classic malware-delivery move:
an executable wearing a document's clothing (invoice.pdf that is really a
Windows PE, photo.jpg that is really an ELF binary, a plain ZIP renamed
report.docx).
It is built for Kali Linux / Parrot OS triage, help-desk intake, and DLP investigations where a renamed executable or a fake image should be noticed before someone double-clicks their way into a bad afternoon.
| Threat | What the filename says | What the bytes say | Byte Bouncer verdict |
|---|---|---|---|
| Malware delivery | cat.jpg |
Windows PE executable | CRITICAL |
| Malware delivery | notes.txt |
ELF / PE executable | CRITICAL |
| Data exfil / evasion | quarterly.jpg |
PDF document | WARNING |
| Container fraud | report.docx |
plain ZIP (no Word structure) | WARNING |
| Corruption / truncation | logo.png |
header too short to match | WARNING |
| Everything normal | memo.docx |
verified Word (OOXML) | OK |
The verdict is explainable — every finding carries the detected type, the expected type, a confidence level, a SHA-256, and a one-line reason you can paste into a ticket or hand to an auditor without interpretive dance.
┌─────────────┐
file ───────▶ │ read header │ first 512 bytes only
└──────┬──────┘
▼
┌─────────────────┐
│ match signatures│ longest/strongest magic wins
└──────┬──────────┘
▼
is it a ZIP (PK\x03\x04)?
│ yes
▼
┌───────────────────────┐
│ peek inside (zipfile) │ central directory only — no bulk
│ docx? xlsx? jar? apk? │ extraction, one tiny member read
│ epub? odt? │
└──────┬────────────────┘
▼
┌──────────────────────────┐
│ compare vs. extension │ + lure/masquerade + double-ext
└──────┬───────────────────┘
▼
verdict: ok · warning · critical · error (+ SHA-256, size, reason)
- Read the header. Only the first 512 bytes are read for identification, so scanning a directory of multi-gigabyte files is cheap.
- Match a signature. Byte Bouncer carries a hand-maintained database of ~50
magic numbers (
signatures.py). When several match, the one with the most distinctive bytes wins. Signature length also drives confidence: an 8-byte PNG magic ishigh; a 2-byteMZ/BMismedium. - Refine ZIP containers.
docx,xlsx,pptx,jar,apk,epub, and OpenDocument files are all ZIPs. Byte Bouncer reads the ZIP central directory (no decompression of untrusted data) to tell a genuine Word document from a plain archive that was merely renamed. - Judge the mismatch and its severity.
- Executable / script content under a trusted, inert-looking extension
(
.jpg,.pdf,.txt,.docx…) → CRITICAL (masquerade). - Any other extension/header disagreement → WARNING.
- A correctly-named executable with a fake inner extension
(
invoice.pdf.exe) → WARNING (double-extension lure). - Agreement, or an unknown-but-harmless file → OK.
- Executable / script content under a trusted, inert-looking extension
(
- Hash for evidence. A streaming SHA-256 is recorded for every file (skip it
with
--no-hashfor faster bulk triage).
The single result object is FileAnalysis:
| field | meaning |
|---|---|
path, extension, size_bytes, sha256 |
file identity |
detected_type, detected_mime, category |
what the bytes say |
expected_types |
what the extension implies |
mismatch (bool) |
do they disagree? |
severity |
ok · warning · critical · error |
confidence |
high · medium · low |
reason |
plain-English explanation |
error |
set when a file could not be read |
cd byte-bouncer
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt # flask, rich, pytest
pip install -e . # optional: exposes the `byte-bouncer` command
cp .env.example .env # optional: for the web dashboardThere is no editable install required to run from source — set PYTHONPATH=src:
PYTHONPATH=src python -m byte_bouncer.cli scan README.mdEvery command prints a compact branded header and honors --plain / --no-color
/ the NO_COLOR env var. When output is not a TTY (piped or redirected) it
automatically degrades to plain, unstyled text. --json is always pure JSON
with zero decoration, safe to pipe into jq.
byte-bouncer scan suspicious.jpg
byte-bouncer scan ~/Downloads --recursive
byte-bouncer scan ./intake --recursive --only-mismatches
byte-bouncer scan ./bulk --recursive --no-hash # faster: skip SHA-256
byte-bouncer scan ~/Downloads --recursive --json > report.json| flag | effect |
|---|---|
-r, --recursive |
recurse through subdirectories |
--json |
pure JSON to stdout |
--no-hash |
skip SHA-256 hashing (faster) |
--only-mismatches |
show only files that failed inspection |
--follow-symlinks |
follow symlinks while recursing (off by default) |
--plain / --no-color |
plain, unstyled text |
Sample output:
┌─────────────────────────────────────────────────────────────────────────────┐
│ 🛂 byte-bouncer │
└───────── checks every file's ID at the door — magic numbers over filenames ─┘
┌─────────┬──────────────────────┬──────┬─────────────────────────┬──────────────────────┬────────┐
│ Verdict │ File │ Ext │ Detected │ Expected │ Conf │
├─────────┼──────────────────────┼──────┼─────────────────────────┼──────────────────────┼────────┤
│ OK │ badge.png │ png │ PNG image image │ PNG image │ high │
│ CRITICAL│ cat.jpg │ jpg │ Windows PE executable │ JPEG image │ medium │
│ OK │ memo.docx │ docx │ Word document (OOXML) │ Word document (OOXML)│ high │
│ CRITICAL│ notes.txt │ txt │ Windows PE executable │ — │ medium │
│ WARNING │ quarterly_selfie.jpg │ jpg │ PDF document document │ JPEG image │ high │
│ WARNING │ report.docx │ docx │ ZIP archive archive │ Word document (OOXML)│ high │
└─────────┴──────────────────────┴──────┴─────────────────────────┴──────────────────────┴────────┘
┌───────────── Possible masquerade — inspect the critical files ──────────────┐
│ 3 ok 2 warning 2 critical 0 error │
└───────────────────────────── 7 files inspected ─────────────────────────────┘
Exit codes (unchanged contract, script-friendly):
0— scan completed, everything matched1— operational error (bad path) or a file could not be read2— at least one extension/header mismatch was found
# CI/pre-ingest gate: fail the pipeline if anything is disguised
byte-bouncer scan ./upload_staging --recursive --json > out.json || echo "flagged!"byte-bouncer signatures # pretty table grouped by category
byte-bouncer signatures --json # the whole database as JSONbyte-bouncer serve # http://127.0.0.1:5061 (loopback only)
byte-bouncer serve --port 8080A small Flask UI: upload one file, get the same verdict rendered as a web report.
| Method | Path | Purpose |
|---|---|---|
GET |
/ |
upload form |
POST |
/scan |
analyze an uploaded file and render a report |
Byte Bouncer is a defensive tool for files on systems you own or are authorized to inspect. It never executes, opens, or renders the files it analyzes — it only reads bytes.
- Loopback by default.
servebinds to127.0.0.1. Exposing it on a routable address is an upload endpoint on your network; only do so on hosts/networks you are authorized to run it on, behind your own authentication. Byte Bouncer warns loudly if--hostis not loopback. - Hostile-input handling. Untrusted ZIPs are treated as hostile: only the
central directory is listed and at most one tiny (
<4 KB)mimetypemember is read — archive contents are never extracted, so ZIP/decompression bombs are a non-event. - Safe file walking. Symlinks are skipped during recursion by default (a link
can point outside the tree you meant to scan, or loop forever). Opt in with
--follow-symlinks. Non-regular files (devices, FIFOs, sockets) are skipped. - Resilient scans. One locked or vanished file reports as an
errorrow instead of aborting the whole run. - Upload limits. The dashboard clamps upload size
(
BYTE_BOUNCER_MAX_UPLOAD_MB, default 16 MB), sanitizes filenames, and inspects uploads only inside a temporary directory that is deleted immediately after. - No secrets in output. Byte Bouncer emits file metadata and hashes only; it never logs credentials or environment values.
--debugonserveenables Werkzeug's interactive debugger (arbitrary code execution by design) — use it on trusted local machines only. Off by default.
cd byte-bouncer
PYTHONPATH=src python -m pytest -qThe suite is fast and dependency-light (no network, no sleeps) and covers signature detection, container-aware ZIP inspection, severity classification, the masquerade / double-extension paths, symlink skipping, error resilience, and the CLI's JSON purity and exit-code contract.
byte-bouncer/
src/byte_bouncer/
signatures.py # magic-number database + categories
analyzer.py # header reading, ZIP refinement, mismatch & severity logic, hashing
ui.py # shared rich Theme + Console + banner (suite look)
cli.py # argparse CLI: scan / signatures / serve
app.py # Flask upload dashboard
templates/ # HTML UI
tests/ # pytest coverage
The signature database is deliberately separate from the CLI and web app: you can
extend signatures.py without touching either.
Limitations (honest ones). This is a curated magic-number table, not libmagic — plain text, unknown/custom binaries, and truncated files won't match and are reported as
Unknown. Some short magics (MZ,BM) are collision-prone and reported atmediumconfidence. For exhaustive coverage, pair Byte Bouncer withfile/libmagic; this tool optimizes for a fast, explainable, dependency-light first-pass control.
A bouncer doesn't care what's printed on your shirt — he checks the ID against your face. This does the same thing with the first 512 bytes of a file. The extension is what a file says it is. The magic number is what it is. When those two disagree somebody is lying, and the genuinely interesting question isn't that they lied — it's how much you should care. An executable wearing a photo's extension is a bad afternoon waiting to happen. A PDF mislabelled as a JPEG is probably just someone's export button misbehaving.
One of ten defensive tools rebuilt to a single standard — understand, reimagine, build the CLI, harden, document, verify. The goal was never to change what these tools are, but to take each one as far as it could honestly go on correctness, safety and the experience of the person actually running it at 2am.
Purpose. A file bouncer that checks every file's ID at the door — magic numbers over filenames — and tells you not just that a file lied about what it is but how much that lie should worry you. An executable wearing a photo's extension is CRITICAL; a PDF mislabeled as a JPEG is a WARNING.
How it works. It reads only the first 512 bytes to match a curated magic-number database, refines ZIP-based containers by reading the ZIP central directory (Word/Excel/PowerPoint OOXML, JAR, APK, EPUB, OpenDocument) without extracting anything, then compares the detected type against the extension and classifies the mismatch by severity. A streaming SHA-256 is recorded as evidence.
Standout CLI. A color-coded results table (OK=green, WARNING=yellow,
CRITICAL=red, ERROR=grey) with paths shown relative to the scanned root, a
transient progress bar over multi-file scans, a summary panel whose headline
changes by worst severity (Clean / Mismatches found / Possible masquerade), and a
signatures subcommand that renders the whole detection DB grouped by category.
Key improvements. Functional: container-aware ZIP refinement flags a plain
archive renamed .docx or a Word file under an .xlsx name; the severity model
(ok/warning/critical) with executable/script masquerades marked CRITICAL; a
curated LURE_EXTENSIONS check that closes a real gap — an executable renamed to a
signatureless extension (malware.exe → notes.txt) used to pass silently;
double-extension lure detection (invoice.pdf.exe); the signature DB expanded from
21 to 54 entries. Security: untrusted ZIPs treated as hostile (central directory
only, at most one <4 KB size-checked mimetype member read — decompression
bombs are a non-event); attacker-controlled filenames escaped before entering
rich markup (closes a crash / markup-injection vector); symlinks and non-regular
files skipped by default during walks; per-file error resilience so one locked
file doesn't abort the scan.
Example.
PYTHONPATH=src python -m byte_bouncer.cli scan ./intake --recursiveThese are defensive tools for systems, files, networks, and people you own or are explicitly authorized to assess. Read this before you run anything:
- Authorization is not optional. Phishing simulations, network scans, IP
reputation lookups, and web-app probing all touch other people's systems or
data. Get written authorization first. Several tools refuse to act until you
assert it (
--yes,--authorized-training,--i-have-authorization,--i-am-authorized, and friends). - Safe by default. Every web UI binds to
127.0.0.1(loopback). Egress, live sends, and active scans are gated behind explicit flags — dry-run, passive, or refuse-and-warn is always the default. - No accidental foot-guns. Flask
--debug(the Werkzeug interactive debugger is remote code execution on any traceback) is warned about loudly and refused entirely off loopback. Untrusted input is size-capped, validated, and rendered inertly so a hostile file or log line can't crash — or take over — your terminal. - No secrets in output. API keys stay in request headers, passwords come from the environment, and nothing sensitive is logged or printed.
These are not offensive tooling. They contain no exploits, no credential harvesters, and no payloads. If a tool could be misused, it is built to resist that misuse by construction.
python -m venv .venv && source .venv/bin/activate
pip install -e .
pytest -q # 26 tests, no network, no sleeps
byte-bouncer --helpReleased under the MIT License. See LICENSE.