ST3GG
From charlesreid1
A steganography SDK, technique catalog, and browser-based analysis workbench. ST3GG is not a general-purpose "point at a mystery file and get the answer" tool. It is a library of encoders, decoders, and detectors you can compose into workflows, a corpus of pre-built example files that demonstrate each technique, and a self-contained browser app (index.html) that exposes both a manual per-technique UI and an LLM-driven agent that orchestrates the same detection primitives.
This page describes the charlesreid1/st3gg fork of the upstream project, with the deprecated NiceGUI and Streamlit UIs removed. ST3GG/Examples has some example usage and scripts.
What ST3GG is (and isn't)
It is:
- A Python library (
steg_core,analysis_tools,crypto,injector) with ~50 registered detection primitives and a well-tested LSB encode/decode engine. - Two CLIs (
stegg,stegg-cli) that wrap the library for common encode / decode / analyze tasks. - A static, self-contained browser app (
index.html) with per-technique tabs (Encode, Decode, Analyze, Text Lab) and an LLM-driven Agent tab that plans and orchestrates the analysis tools. - A corpus of 100+ pre-encoded example files, one per technique, that serve as test fixtures, a detection benchmark, and a learning catalog.
It is not:
- A blind decoder. Decoding an arbitrary stego file with no prior information is an unsolved problem in the general case. The tools here work when either (a) you know which technique was used, or (b) the technique is common enough to be caught by a sweep (Smart Scan, ALLSIGHT, the Agent's exhaustive mode).
- An "install and it just decodes everything" product. Each tab in the browser app, and each function in the library, reads exactly one class of technique. The Agent tab is the closest thing to a general-purpose driver, but it's an LLM-planned loop - not a fixed pipeline - and it costs API credits.
Well-developed entry points in this fork:
stegg- interactive CLI (Rich terminal output)stegg-cli- agent-friendly CLI (JSON output, subprocess-safe)stegg-tui- Textual-based terminal UI (partially supported; see caveats)index.html- the static browser app, including the Agent tab- Python API -
import steg_core,import analysis_tools,import injector,import crypto
The NiceGUI (webui.py / stegg-web) and Streamlit (app.py) UIs that shipped in upstream have been removed from this fork; they were incomplete, missed the Agent and metadata inspection tabs, and caused confusion about which interface to use. There is no MCP server despite what the upstream README implies - the stegg-mcp script and [mcp] extras group are not implemented.
Install
pip install stegg # core CLIs + Python API pip install stegg[crypto] # + AES-256-GCM encryption support pip install stegg[tui] # + Textual TUI pip install stegg[all] # everything
Or from source:
git clone https://github.com/charlesreid1/st3gg.git cd st3gg pip install -e .
The browser app needs no install - see the "Browser app" section below.
The two CLIs
stegg - interactive use
Rich terminal output with tables and colors, made for humans at a keyboard.
# Encode a message into a PNG (STEG-headered LSB, RGB 1-bit, sequential) stegg encode -i carrier.png -t "your secret" -o out.png # Decode a file you know was made with stegg's LSB encoder stegg decode -i out.png # Run detection sweep on a suspicious image stegg analyze suspect.png --full
Additional subcommands: capacity, detect, and the injector commands under stegg --help.
stegg-cli - subprocess / agent use
Same operations, but compact JSON output, no interactive prompts, and designed so its output doesn't bloat an LLM's context window when driven as a subprocess.
stegg-cli encode -i carrier.png -t "secret" -o out.png stegg-cli decode -i out.png stegg-cli analyze suspect.png --full stegg-cli detect suspect.png # header-only detection stegg-cli capacity carrier.png --channels RGB --bits 1 stegg-cli list-tools # enumerate the 50-ish detectors stegg-cli analysis-tool suspect.png <ACTION> # run one detector by name stegg-cli inject-chunk -i in.png -o out.png --keyword Comment --text "hi" stegg-cli read-chunks suspect.png # dump PNG tEXt/iTXt/zTXt chunks
stegg-cli list-tools is the fastest way to discover what detectors are available at any given moment. Every entry in that list is also callable from Python via analysis_tools.TOOL_REGISTRY.
Python API - the actual core
The CLIs and the browser app are thin wrappers over these primitives. If you're building anything nontrivial (a triage pipeline, a detection dashboard, an integration into your own agent), use these directly.
Encode / decode LSB
from steg_core import encode, decode, StegConfig, get_channel_preset
from PIL import Image
img = Image.open("carrier.png")
config = StegConfig(channels=get_channel_preset("RGB"), bits_per_channel=1)
stego = encode(img, b"hidden payload", config)
stego.save("stego.png")
payload = decode(Image.open("stego.png"))
print(payload.decode())
StegConfig covers 15 channel presets x 8 bit depths x 4 strategies (sequential, interleaved, spread, randomized) = 120 combinations. encode writes a STEG magic-byte header so decode can round-trip without you re-specifying the config.
Detect without extracting
from steg_core import detect_encoding
from PIL import Image
result = detect_encoding(Image.open("suspect.png"))
# returns the detected config if a STEG header is present, else None
Sweep all analysis tools
from analysis_tools import TOOL_REGISTRY, execute_action, list_available_tools
# Enumerate every detector
for name in list_available_tools():
print(name)
# Or iterate via the registry (same set, richer metadata)
for tool in TOOL_REGISTRY.list_tools():
print(tool.name, "-", tool.description)
# Individual detectors are callable as normal functions:
from analysis_tools import detect_unicode_steg
result = detect_unicode_steg(open("message.txt", "rb").read())
if result["found"]:
print(f"Invisible chars: {result['invisible_chars']}")
TOOL_REGISTRY is the closest thing to "one call runs everything." A blind-triage pipeline is built by iterating this registry against a file, normalizing verdicts, and ordering by cheap to expensive. The Agent tab in the browser app does exactly this with an LLM in the loop; see below.
PNG metadata injection & inspection
from injector import (
inject_text_chunk, inject_itxt_chunk, inject_private_chunk,
read_png_chunks, extract_text_chunks,
)
inject_text_chunk("in.png", "out.png", keyword="Comment", text="hi")
chunks = read_png_chunks("out.png") # all chunks
texts = extract_text_chunks("out.png") # just the readable ones
This is the technique that hides data in PNG tEXt / iTXt / zTXt chunks - the same trick used by examples/example_metadata.png and by the banner image.
Encryption (optional)
from crypto import encrypt, decrypt # requires pip install stegg[crypto]
ct = encrypt(b"plaintext", password="hunter2", method="aes")
pt = decrypt(ct, password="hunter2", method="aes")
Pair with encode() to hide encrypted payloads.
The browser app (index.html)
The static browser app is the most complete interface. It's a single file with inline JS/CSS and no server dependency - every detection routine is reimplemented in JavaScript so it runs entirely in the browser tab.
Launch it:
# From the repo root open index.html # Or serve it locally if your browser dislikes file:// URLs python3 -m http.server 8090 # then visit http://127.0.0.1:8090
Tabs:
- Encode - hide a payload in an image using LSB.
- Decode - extract from an image using LSB, either auto (STEG header) or manual (pick channels/bits/strategy).
- Analyze - the diagnostic dashboard: chi-square, bit-plane visuals, metadata inspector (EXIF / PNG chunks / IPTC / XMP / ICC / hidden data), trailing-data detection, capacity table. This is where non-LSB techniques like metadata-stored payloads become visible.
- Text Lab - Unicode steganography detection and encoding (zero-width, homoglyphs, variation selectors, whitespace).
- Agent - an LLM-driven analysis loop; see next section.
- Matryoshka and SPECTER - conditionally-shown advanced modes for nested and channel-hopping steganography.
The key thing to internalize: each tab reads exactly one class of technique. If you drop example_metadata.png on the Decode tab, it will fail - the payload is in PNG text chunks, not in pixel LSBs. Use the Analyze tab (or the Agent tab) for that one. The tabs are the technique catalog, and mismatching file to tab is the single most common source of "why doesn't this work" confusion.
The Agent tab - how it actually works
The Agent tab is what upstream calls "ALLSIGHT" and gets closest to a general-purpose triage tool. It is not an MCP server, does not call the Python venv, and does not invoke stegg-cli or stegg-mcp. Here's the actual architecture:
- Tools live in the browser. A JS object
AI_AGENT_TOOLSinsideindex.htmldefines ~40 tools (smart_scan,extract_metadata,detect_trailing_data,parse_file_structure,fuzz_all_channels,detect_text_steg,scan_embedded_files, etc.). Each has a name, description, JSON parameter schema, and anexecutefunction - pure JavaScript that runs against the canvas and raw file bytes already in memory. - The LLM is a planner, not a worker. When you click "Reveal," the app builds a system prompt containing the tool descriptions and file context, then POSTs it to
https://openrouter.ai/api/v1/chat/completionsusing yoursk-or-...OpenRouter key. OpenRouter routes to whichever model you picked (Claude, GPT, Gemini, or any free model on the platform). - Tool calls are prompted JSON, not native tool-use. The system prompt instructs the model to reply with a JSON code block containing a
tool_callsarray. The browser parses that block, dispatches each call to the localAI_AGENT_TOOLSobject, runs the JS, and sends the results back as the next user turn. - The loop continues until the model emits
{"action": "conclude", ...}or a max-iterations cap is hit.
Consequences:
- All analysis happens locally in the browser. Your file bytes never leave the machine, except when "vision mode" is checked - that sends a base64 PNG of the canvas to OpenRouter alongside the messages.
- OpenRouter is used only as an LLM proxy. It has no idea what MCP is, doesn't spawn tools, doesn't touch your venv, and doesn't know about the Python library. It only sees the messages the browser sends it.
- You pay per token on OpenRouter. A single "Reveal" run typically fits within a few cents on paid models, or is free on OpenRouter's zero-cost free-tier models (which are noticeably worse at following the prompted-JSON contract).
- Exhaustive Mode adds a per-file-type checklist to the system prompt and forces the loop to walk every technique in the checklist before concluding. Higher recall, more API calls.
If you want to build the same idea for a Python agent (Claude Code, an MCP-speaking desktop app, or your own custom harness), the JS tools in AI_AGENT_TOOLS are almost 1:1 rewrites of Python functions in analysis_tools.py. Wrapping TOOL_REGISTRY as MCP tools would give you the equivalent for stdio-based agents - but that wiring is not implemented in this fork despite upstream hints to the contrary.
The examples/ directory
Not tutorials. Not throwaway test data. This directory is a technique catalog - one file per steganographic technique, all containing the same known payload (the "Plinian divider" string), used as human-readable reference, as the fixture set for the automated tests, and as ground truth for detection benchmarks.
Purpose
- Technique demonstration. Each file shows exactly what one specific stego method produces. Open it, use the matching tool, see the output. Fastest way to build intuition for what "PNG tEXt chunk stego" or "Unicode variation selector stego" looks like in practice.
- Test fixtures.
test_examples.pyiterates the directory and verifies every technique still round-trips. If a detector regresses, this catches it. - Detection benchmark. For anyone writing a new detector or triage pipeline, this is a ground-truth dataset - every file has a known technique and known payload, so recall and precision are directly measurable.
- Learning resource. Reading
generate_examples.pyalongside a specific example file is the shortest path from "what is technique X?" to "here's the exact code that produces it."
Key files in examples/
generate_examples.py- the generator. Rebuilds every example file from scratch. Read this if you want to see how any specific technique is implemented; each is one function.README.md- the catalog index. Lists every example file with its technique and hidden payload, grouped by modality.st3gg_banner.png- the README banner. Hides a flag in PNGtEXt/iTXtchunks and appends more data after the IEND marker. LSB decoding will find nothing; the Analyze tab (orstegg-cli read-chunks) reveals both payloads. A perfect example of why the Decode tab is the wrong tool for the wrong technique.example_lsb_rgb.png- the canonical LSB starting point. Standard 1-bit LSB across RGB channels with a proper STEG header.stegg decode -i example_lsb_rgb.pngwill just work.example_metadata.png,example_png_chunks.png,example_trailing_data.png- three different PNG-based techniques that are not LSB. Payloads live in metadata chunks or after the IEND marker. Use the Analyze tab orinjector.read_png_chunks().example_zero_width.txt,example_homoglyph.txt,example_invisible_ink.txt,example_variation_selector.txt, etc. - Unicode/text techniques. Read withanalysis_tools.detect_unicode_steg()or the Text Lab tab.example_lsb.{bmp,gif,tiff,ppm,pgm,ico,webp,aiff,au,wav}- LSB across every image and audio format the library supports; useful for verifying that format-specific readers still work.example_hidden.{py,js,c,css,html,json,yaml,xml,pdf,zip,tar,gz,sqlite,mid,pcap,...}- one per document / code / archive / network format, each demonstrating a format-appropriate technique (comments, metadata streams, header fields, packet payloads, etc.).example_*.pcap- network-layer techniques (DNS tunneling, ICMP payload, TCP covert channel, HTTP headers) captured as pcaps you can open in Wireshark.
How to actually use this directory:
- Pick a technique you want to understand.
- Open the matching example file to see what it looks like.
- Open
generate_examples.pyand find the function that produced it - that's the encoder. - Find the matching detector in
analysis_tools.TOOL_REGISTRY(orstegg-cli list-tools). - Run the detector against the example file - that's your working proof.
That loop - example file, generator function, detector function - is the intended path for learning the library and for building anything on top of it.
When to reach for what
| You have | You want | Use |
|---|---|---|
A file you encoded yourself with stegg |
The payload back | stegg decode, or the Decode tab
|
| A file with a known non-LSB technique | The right reader | The specific function in analysis_tools, or the matching browser tab (Analyze for metadata, Text Lab for Unicode, etc.)
|
| A suspicious file, no prior info, willing to click | A broad sweep | The browser app's Analyze tab first, then the Agent tab (needs OpenRouter key) |
| A suspicious file, no prior info, want it scriptable | A programmatic sweep | Iterate TOOL_REGISTRY in Python, or shell-loop stegg-cli analysis-tool per detector
|
| A whole batch of files to triage | A pipeline | The Python API - steg_core, analysis_tools, injector, crypto - this is what the library is for
|
| To learn a specific technique | Working reference | The matching file in examples/ + its generator in generate_examples.py
|
Testing
python test_examples.py # verify every example round-trips python test_comprehensive.py # full test suite (568 tests) python test_matryoshka.py # nested-steganography tests
Caveats specific to this fork
- References to non-existent MCP server removed. The upstream README references
stegg-mcpandpip install stegg[mcp]. Neither exists in the codebase (there's nomcp_server.py, no MCP extras group, no script entry). If you want an MCP wrapper aroundTOOL_REGISTRY, you'd have to write it. - No NiceGUI or Streamlit UI. Those upstream frontends were removed in this fork because they lacked the Agent, Text Lab, and metadata-inspection tabs that
index.htmlhas, and their existence caused confusion about which interface was the "real" one. - The TUI (
stegg-tui) is included but rough. It installs viapip install stegg[tui]but has known bugs. The two CLIs and the browser app are the polished paths.
Flags