Stegstr
Embeds messages steganographically into images with the --robust JPEG/QIM encoder so the hidden payload survives Instagram's own re-encoding pipeline; verified live with a real Instagram send where the file was re-encoded and the payload still decoded correctly.
Supports embedding robust steganographic payloads intended for images sent through Telegram, with the platform's known resize/quality settings validated via the local channel simulator rather than a live send.
Targets WhatsApp image sharing for steganographic payload delivery using the --robust JPEG/DCT (QIM) encoder, plus a channel-fingerprinting workflow (calibrate) that compares a sent original against the file received back to infer WhatsApp's re-encode pipeline and recognize its pass-through behavior.
Stegstr
Steganographic social networking. Hide messages in images and share them anywhere — local-first, with optional Nostr sync. This fork of brunkstr/Stegstr fixes 9 bugs — 5 pre-existing in upstream (including a Nostr signature-spoofing vulnerability), 3 more found in its own new work — and adds JPEG-domain steganography that survives WhatsApp and Instagram recompression — confirmed with real sends, not just simulation.
Download v0.1.0
No Rust, no Node, no build step. Pick your platform:
Platform | Download |
Windows | Stegstr-Windows.exe (or .msi) |
macOS | |
Linux |
All builds and the release itself run in GitHub Actions from a clean clone, not a local machine. SHA256SUMS.txt is in the release if you want to verify your download (sha256sum -c SHA256SUMS.txt, or certutil -hashfile <file> SHA256 on Windows).
Verified before publishing, not just built: the Windows download was installed and launched for real — an actual app window opened and stayed running — and the bundled CLI was round-tripped (embed → decode, byte-exact) before this link went out. The Linux AppImage is checksum-verified and the correct size, but launching it was not tested — this was built and verified from a Windows machine with no Linux environment available. If you're on Linux, you're the first real launch test; let us know if something's wrong.
Related MCP server: img-convert MCP Server
What this fork fixes
5 bugs pre-existing in the upstream application — each confirmed by building and running a pristine clone of brunkstr/Stegstr @ ad2e10e directly, not inferred from reading the code:
A Nostr signature-spoofing vulnerability — received events were never cryptographically verified. Any relay (or a MITM) could inject events that display as posted by any pubkey, including someone else's. (full writeup)
Silent payload corruption on the default encoder — the default
embed(no flags) returns wrong bytes on decode for any cover ≥256px with a payload over ~9 bytes, no error, just wrong data. (full writeup)A ~9dB image-destruction bug — encoding into any cover wider than one 256px tile visibly wrecked the image itself (a measured PSNR of 8.96dB / 9.02dB on two covers, against 30-67dB everywhere else) — the payload still decoded fine, which is exactly why every prior correctness test missed it; nothing had compared the output image to the cover pixel-for-pixel. Evidence images show the corruption directly. (full writeup)
Decode flipped bits on high-contrast/noisy covers (unguarded clamping in the inverse transform). (BUGS.md #2)
Decode's tile-aligned search silently skipped for extreme aspect ratios (same corruption class as above, different geometry). (BUGS.md #4)
Separately: 3 bugs found and fixed in this fork's own new work — these aren't upstream's fault, they're in code upstream doesn't have (the new JPEG/QIM encoder, and the Nostr publish-confirmation feature):
A malformed/legacy JPEG could leak a file handle and libjpeg's memory pool on every decode attempt. (BUGS.md #3)
Publish silently treated "1 of 5 relays confirmed" the same as "5 of 5" in the UI. (BUGS.md #7)
Security-relevant: an old QIM image (pre-adaptive-delta header format) could crash the app outright via a Reed-Solomon buffer underflow — a malformed/mismatched header, not just an incompatibility. Fixed with a bounds check plus a versioned decoder that still reads the old format. (BUGS.md #8)
One more, found in a later regression pass, that doesn't cleanly fit either group above: the default decoder trusted a file's extension over its actual content, so a valid image saved with the "wrong" extension failed with a misleading "corrupt file" error. The faulty line is identical to upstream's, but upstream has no second encoder and never promises extension-agnostic decoding — the bug only became reachable once this fork's own dual-encoder decode path existed. (BUGS.md #9)
Plus repo and toolchain fixes: 8 npm vulnerabilities (1 critical), a broken mobile-android git submodule failing every CI checkout, no declared Rust MSRV, and outdated GitHub Actions. Full detail on all 9 application bugs, with repro steps, root cause, fix commit, and regression test each: BUGS.md. Consolidated report with before/after numbers and an honest "what we did not test" section: ROBUSTNESS_REPORT.md.
Live platform results — stated precisely
"Survives recompression" and "passes through untouched" are different claims. Both are documented, not conflated:
Platform | Result | What was actually shown |
Live, genuine survival | Sent through real Instagram: file size changed (+9.1%, 48,241 → 52,609 bytes) — Instagram actually re-encoded it — and the payload still decoded correctly. | |
Live, but pass-through, not survival | Sent through real WhatsApp: came back byte-identical. A control test (an untouched, unembedded original sent the same way) came back ~76% smaller, proving WhatsApp's pipeline does recompress — just not this file. The stego output is small/compliant enough that WhatsApp's own pipeline treats it as a no-op, so this is not evidence the payload survived a recompression pass. | |
Telegram | Simulated only | Same code path validated for WhatsApp/Instagram, run through a local simulator matching Telegram's known resize/quality settings — not sent through a real Telegram client. |
Full methodology, the control-test details, and the simulated 45/45 channel matrix (9 cover types × 5 platform profiles): channel_simulator/BASELINE_RESULTS.md.
Usage
You need Rust (latest stable) to build the CLI, or use a downloaded binary above.
./stegstr-cli post "Hello from CLI" --output bundle.json
./stegstr-cli embed cover.png -o out.png --payload @bundle.json --encrypt
./stegstr-cli detect out.pngSending through WhatsApp, Instagram, or Telegram? Add --robust — it switches to the JPEG/DCT (QIM) encoder built to survive those platforms' own re-compression (the example above, without --robust, does not survive being re-uploaded). Output is always a .jpg:
./stegstr-cli embed cover.jpg -o out.jpg --robust --payload "hello world"
# send out.jpg through WhatsApp/Instagram/Telegram, download the received copy, then:
./stegstr-cli decode received.jpg
# decode tries the robust JPEG/QIM decoder first, then falls back to PNG/DWT --
# you don't need to know which encoder produced an image you were sent.AI agent operability
The CLI and an MCP server are both first-class here, not an afterthought —
skill/stegstr/ documents the full zero-human-input flow,
and every claim below is verified against the real binary in
src-tauri/tests/cli_json_schema.rs and tests/e2e/agent_smoke.sh.
--jsonon every command (decode,detect,embed,post,calibrate) — exactly one JSON object on stdout, no prose mixed in. Schemas are committed atschema/cli/and validated against the actual binary in CI-runnable tests, not just documented and hoped-for.Documented, stable exit codes:
Code
Meaning
0Success
1Invalid usage or an otherwise-unclassified error
2Capacity exceeded — payload doesn't fit the cover
3No payload found in the image
4Decryption failure (
--encrypt/--decrypt)5Malformed input (unreadable image, bad base64/hex, non-UTF-8 text)
In
--jsonmode the exit code always matches the emittederror.kind.No interactive prompts, ever — nothing in this CLI reads stdin interactively, on a TTY or not.
--yesis accepted everywhere as a no-op, for scripts that want to pass it defensively.stegstr-cli calibrate— channel fingerprinting: compare a sent original against the file received back after a real platform round trip, and infer that platform's actual re-encode pipeline (resize rule; exact JPEG quality recovered from quantization tables, or a clearly labeled best-fit estimate when it isn't exact; chroma subsampling; progressive/baseline; whether metadata was stripped). Verified against this repo's own real captured Instagram and WhatsApp evidence inlive_test/— correctly recovered exact JPEG quality for both, and independently inferred WhatsApp's ~1600px longest-side resize from pixel dimensions alone.stegstr-cli mcp— an MCP server over stdio (built on the officialrmcpSDK) exposingembed,decode,detect, andcalibrateas tools, each with a typed input schema and description, returning the identical JSON shape as the matching CLI command.
What isn't included in this pass, honestly: binary-safe stdin/stdout
piping for embed/detect, and a --seed flag for deterministic output
where randomness is used (event keys, the QIM permutation) — both are in
the original AI-agent-operability brief but weren't built here; flagged as
open scope, not silently dropped.
Verify it yourself
./scripts/verify.sh # clean clone -> Rust build/test/clippy -> npm test -> full channel matrix
./scripts/demo.sh # fixed cover, fixed payload: embed + decode round tripWindows: scripts\verify.ps1 (same steps, PowerShell). Needs Rust, and optionally Node.js + Python for the frontend tests and channel-simulator matrix (skippable: ./scripts/verify.sh --skip-python).
Expect this to take a while on a cold machine — roughly 10-20 minutes with no warm caches (network- and CPU-dependent), most of it the Rust release build alone (measured at ~7-8 minutes from an empty target/). The script prints a heartbeat line every 20 seconds during long steps and a [step N] marker with elapsed time per step, so a quiet stretch is normal, not a hang — cargo's own Compiling <crate> output is the sign of life during the build itself.
If npm test reports a timeout waiting for a worker process to start, that's a resource-contention flake (something else on the machine was compiling at the same moment), not a real test failure — it doesn't happen when nothing else is running concurrently. Just re-run it.
Build from source (full app)
Prerequisites: Node.js 18+, Rust (latest stable).
git clone https://github.com/brunkstr/Stegstr.git
cd Stegstr
npm install
npm run build:mac # or build:win, build:linuxSee the repo for platform-specific build deps (e.g. Xcode CLI tools, Visual Studio Build Tools, Linux dev packages). CLI-only:
cd src-tauri && cargo build --release --bin stegstr-cliBinary: target/release/stegstr-cli (Windows: stegstr-cli.exe).
Links
Latest release — downloads for all platforms
Robustness report — before/after numbers, live-platform confirmation, what wasn't tested
Bugs found and fixed — 9 bugs (5 pre-existing upstream, 3 in this fork's own work, 1 that's neither), repro steps, root cause, fix, regression test each
Evidence images — visual proof of the image-destruction bug, upstream vs. fixed
Agent skill — zero-human-input CLI/MCP workflow for AI agents, every command verified
CLI JSON schemas —
--jsonoutput shapes for every commandWebsite — Downloads, getting started, wiki
Wiki / CLI docs — Full CLI reference
License
MIT
This server cannot be deployed
Maintenance
Related MCP Connectors
Inspection, steganography and forensics API for files and images. Bitcoin pay-per-use.
Image toolkit: resize, compress, crop, watermark, convert, rotate, EXIF read/strip.
Image & PDF tools for AI agents: compress, convert, resize, PDF, AI vision, pipeline.
- MochifyOAuthapp.mochify
Image and PDF toolkit: convert to AVIF/WebP/JXL, resize, crop, remove backgrounds, optimize PDFs.
Related MCP Servers
- AlicenseAqualityDmaintenanceInspect and remove EXIF metadata locally through MCP tools. Supports reading EXIF, detecting GPS, summarizing privacy risks, and stripping EXIF from images.113MIT
- AlicenseNot gradedqualityCmaintenanceProvides AI agents with tools to convert images between formats and inspect image metadata, enabling seamless image processing within agent workflows.50 npm3MIT
- AlicenseNot gradedqualityFmaintenanceProvides local image analysis tools using Pillow for dimensions, colors, metadata, and format conversion without external APIs.1MIT
- AlicenseBqualityCmaintenanceThe most comprehensive steganography analysis toolkit for AI agents, providing 60 tools across 7 categories for detecting and extracting hidden data from images, audio, text, and files, all running 100% offline.10082 npm4MIT