A Flask web interface for Meow Decoder that supports the standard offline sender and recovery flow, with advanced and experimental modes available for exploration.
If you are new to the web demo, start with the standard encrypted transfer flow:
- Upload a file or message.
- Enter a password.
- Generate the transfer.
- Keep the transfer visible while the receiver scans it.
- Export the captured transfer from the receiver.
- Recover the original file on desktop.
That is the default story this demo supports best.
| Maturity | What belongs here |
|---|---|
| Recommended | Standard encrypted sender flow and desktop recovery |
| Advanced | Fountain tuning, alternate modes, diagnostics and deployment options |
| Experimental | Cat Mode camouflage, duress-heavy or deniability-focused workflows |
- π Encode Files: Upload any file (up to 8 MB) and convert to animated GIF
- π Decode GIFs: Recover original files from Meow Decoder GIFs
- π» Cat Mode: Optional experimental camouflage and blinking-eye transport demos
β οΈ Duress Mode: Dual-password plausible deniability- π Fountain Codes: Configurable redundancy for frame loss tolerance
- π AES-256-GCM: Military-grade encryption with Argon2id key derivation
- Python 3.10+
- System dependencies for pyzbar:
- Ubuntu/Debian:
sudo apt-get install libzbar0 - macOS:
brew install zbar - Alpine Linux:
apk add zbar
- Ubuntu/Debian:
-
Clone the repository (if not already done):
cd /workspaces/meow-decoder -
Install Python dependencies:
pip install -r web_demo/requirements.txt pip install -r requirements.txt # Core meow_decoder dependencies -
Run the Flask app:
cd web_demo python app.py -
Open in browser:
http://localhost:5000
If you're running in a GitHub Codespace:
# Install dependencies
pip install -r requirements.txt
pip install -r web_demo/requirements.txt
# Run the app
cd web_demo
python app.pyThe Codespace will automatically forward port 5000, and you'll get a notification with a clickable URL.
- Sign up for a free account at PythonAnywhere
- Open a Bash console and clone the repository:
git clone https://github.com/systemslibrarian/meow-decoder.git cd meow-decoder
mkvirtualenv meow-decoder --python=python3.10
workon meow-decoder
# Install dependencies
pip install -r requirements.txt
pip install -r web_demo/requirements.txt- Go to Web tab in PythonAnywhere dashboard
- Click Add a new web app
- Choose Manual configuration (not Flask wizard)
- Select Python 3.10
Click on the WSGI configuration file link and replace contents with:
import sys
import os
# Add project directory to path
project_home = '/home/YOUR_USERNAME/meow-decoder'
if project_home not in sys.path:
sys.path.insert(0, project_home)
# Add web_demo directory to path
web_demo_dir = os.path.join(project_home, 'web_demo')
if web_demo_dir not in sys.path:
sys.path.insert(0, web_demo_dir)
# Import Flask app
from app import app as applicationReplace YOUR_USERNAME with your PythonAnywhere username.
In the Web tab, scroll to Virtualenv section and enter:
/home/YOUR_USERNAME/.virtualenvs/meow-decoder
cd ~/meow-decoder/web_demo
mkdir -p instance/uploads instance/outputs
chmod 755 instance- Click the Reload button in the Web tab
- Visit your app at
https://YOUR_USERNAME.pythonanywhere.com
Default: 50 MB upload limit (set in app.py line 30), with an 8 MB limit for encoding operations (enforced at app.py line 130).
To change the upload limit:
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16 MBDefault: txt, pdf, png, jpg, jpeg, gif, bin, zip, doc, docx
Edit in app.py line 43:
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'bin', 'zip'}Files older than 30 minutes are automatically deleted.
To change, edit app.py line 50:
cleanup_old_files(max_age_minutes=60) # 1 hourTo disable SchrΓΆdinger or Duress modes server-side, edit templates/encode.html and remove the corresponding <option> tags from the mode selector dropdown.
Cat Mode is an optional advanced or experimental surface. It should not be mistaken for the default product story of the web demo.
Cat Mode in this web demo has two related presentations built on the same core password-based encryption:
- Cat carrier mode hides QR codes in cat imagery for camouflage.
- Blinking-eye mode sends ciphertext optically as green/dark eye states.
Both use AES-256-GCM + Argon2id for the payload first. The cat visuals change the transport or presentation layer; they do not replace the cryptography.
For the carrier-image path:
- Encoder uses bundled cat carrier images (
assets/demo_logo_eyes.gif) - QR codes are embedded steganographically in photographic cat images
- Output uses APNG (lossless animated PNG) β GIF palette quantization destroys LSB stego data
- Decoder automatically detects and extracts QR codes from cat camouflage via LSB extraction fallback
- UI displays Cat Mode badge (π») on encoded files
- Steganography Level: Defaults to level 2 (balanced visibility/capacity)
- Carrier Images: Bundled cat photos from
assets/directory - Output Format: APNG (
.png) β lossless pixel preservation for stego fidelity - Redundancy: 2.5Γ fountain code redundancy (compensates for any stego channel noise)
- Plausible Deniability: QR codes look like cat photos at casual inspection
- Scanning: Decoder auto-detects stego mode and extracts LSBs before QR scanning
- Crypto: AES-256-GCM + Argon2id before visual transmission
- Transport: Eye color encodes ciphertext bits (green = 1, dark = 0)
- Server route: The Flask Cat Mode page uses the production Argon2id preset and password-only mode
- Mode scope: No forward secrecy or post-quantum mode on the blinking-eye route
- Why APNG, not GIF? GIF uses 256-color palette quantization which corrupts LSB-embedded data. APNG is lossless, preserving all embedded pixel values through save/load cycles.
- Stego Extraction Fallback:
decode_gif.pyfirst tries direct QR scanning. If no QR codes are found (stego frames look like photos), it automatically tries LSB extraction at depths 2, 1, and 3. - Frame MAC Tracking: Original GIF/APNG frame indices are preserved through extraction so per-frame MAC verification uses the correct index.
- Cat Mode is cosmetic camouflage (not forensic-proof steganography)
- Blinking-eye Cat Mode changes transport only; it is not a separate encryption mode
- Output is APNG format (
.png), not GIF β some viewers may not animate APNG files - Best used for aesthetic purposes and casual plausible deniability
This is a demonstration instance. The UI displays a banner warning:
β οΈ Demo Environment Only This is a demonstration instance. Do not upload sensitive files. Files are automatically deleted after 30 minutes.
For production use:
- Deploy on trusted infrastructure (your own server, not public host)
- Use HTTPS (mandatory for password security)
- Implement authentication (add user accounts)
- Audit logs (track who encodes/decodes what)
- Rate limiting (prevent abuse)
Decoding errors return uniform messages that don't reveal:
- Whether the password was incorrect
- Whether duress mode was involved
- Whether the file was corrupted
This prevents timing attacks and distinguishers that could compromise plausible deniability.
- Passwords are never logged or stored
- Argon2id key derivation runs on server (intentional: prevents weak client devices)
- Use strong, unique passwords (20+ characters recommended)
Install system dependencies:
# Ubuntu/Debian
sudo apt-get install libzbar0
# macOS
brew install zbar
# Alpine Linux
apk add zbarchmod 755 web_demo/instance
chmod 755 web_demo/instance/uploads
chmod 755 web_demo/instance/outputsFile exceeds 8 MB limit. Either:
- Compress the file before encoding
- Increase
MAX_CONTENT_LENGTHinapp.py(not recommended for public demos)
Verify bundled carrier exists:
ls -lh assets/demo_logo_eyes.gifIf missing, clone the full repository (carrier images may not be in minimal distributions).
Possible causes:
- Wrong password
- Corrupted GIF file
- Not a Meow Decoder GIF (must be created by this tool)
- Cat Mode camouflage too aggressive (try re-encoding with lower stego level)
web_demo/
βββ app.py # Main Flask application
βββ requirements.txt # Python dependencies
βββ wsgi.py # PythonAnywhere WSGI file
βββ start.sh # Startup script
βββ README.md # This file
βββ test_all_modes.py # Full test suite β all 6 modes, 5 runs each
βββ test_cat_mode.py # Cat mode integration test (primary demo verification)
βββ test_cat_e2e_speeds.py # Cat eye-blink video E2E speed test (requires running Flask app)
βββ static/ # Static assets (WASM, JS, CSS)
βββ templates/
β βββ base.html # Base template with navigation
β βββ cat_mode.html # Cat mode page
β βββ decode.html # Decoding page
β βββ decode_result.html # Decoding result page
β βββ demo.html # Demo page
β βββ encode.html # Encoding page
β βββ modes.html # Mode selection page
β βββ result.html # Encoding result page
β βββ schrodinger.html # SchrΓΆdinger mode page
β βββ webcam.html # Webcam scanner page
βββ instance/ # Runtime data (gitignored)
βββ uploads/ # Uploaded files (temp)
βββ outputs/ # Generated GIFs (temp)
- Add mode to
templates/encode.htmldropdown - Update
encode_page()inapp.pyto handle new mode - Add mode-specific configuration logic
- Add mode badge CSS in
templates/base.html - Update mode emoji in
get_mode_emoji()function
The web demo includes three automated test scripts located in web_demo/. Run them from the repository root with the virtual environment active:
cd /workspaces/meow-decoder
source .venv/bin/activateThe primary test to verify the demo works end-to-end. Runs a full encode β stego embed β decode roundtrip using the real demo_logo_eyes.gif carrier asset. Use this first to confirm Cat Mode is functional.
python web_demo/test_cat_mode.pyWhat it tests:
- Finds and loads the
assets/demo_logo_eyes.gifcarrier - Encodes a small text file with Cat Mode steganography (
stego_level=2) - Writes output as APNG (not GIF β GIF palette quantization destroys LSB data)
- Decodes the APNG and verifies the recovered content matches the original
Expected output on success:
π± Testing Cat Mode Integration...
β
Cat carrier found: ...
π Encoding with Cat Mode...
β
Encoding successful!
π Decoding Cat Mode output...
β
Decoding successful!
β
Content matches original!
π Cat Mode test PASSED!
Runs 5 trials each of all supported encoding modes to confirm broad pipeline health. This is the most comprehensive test.
python web_demo/test_all_modes.py
# Verbose output (shows per-trial timing and stats):
python web_demo/test_all_modes.py --verboseModes covered:
- Normal Mode β Standard QR encoding
- Cat Mode β Steganographic encoding with cat carrier
- Cat Mode Server API β Binary encrypt/decrypt for eye-blink transmission
- Duress Mode β Panic password reveals decoy data
- Forward Secrecy Mode β X25519 ephemeral key exchange (MEOW3)
- SchrΓΆdinger Mode β Dual-secret quantum plausible deniability
Uses
MEOW_TEST_MODE=1automatically (fast 32 MiB Argon2id) so it completes in seconds rather than minutes.
This is the real-world demo verification test. It simulates the exact user flow where the cat's eyes blink out an encrypted message as a video, the browser is refreshed (no carryover state), the video is uploaded, and the password is entered to decode it. Use this to confirm the full demo pipeline works end-to-end, not just the encryption layer.
It tests 5 blink speeds Γ 3 trials = 15 full roundtrips:
- POST
/cat-mode-encrypt-serverβ get encrypted payload hex - Generates a synthetic eye-blink video (AVI/MJPEG) matching the JS canvas output
- Simulates a browser refresh (new session, zero carryover state)
- POST
/cat-mode-decode-videoβ decode binary bits from the video - POST
/decode-cat-binarywith the password β verify full decryption succeeded
Blink speeds tested: 200ms, 150ms, 100ms, 83ms, 50ms per bit
Why two terminals: The Flask app must be running as a live server because the test makes real HTTP requests to it β it cannot be imported or mocked. MEOW_TEST_MODE=1 is required on both to use fast Argon2id (32 MiB, 1 iteration) instead of production settings (512 MiB, 20 iterations), otherwise each of the 15 trials takes ~30 seconds just for key derivation.
Terminal 1 β start the Flask app:
cd /workspaces/meow-decoder
source .venv/bin/activate
MEOW_TEST_MODE=1 python web_demo/app.pyWait until you see Running on http://127.0.0.1:5000, then open a second terminal:
Terminal 2 β run the e2e test:
cd /workspaces/meow-decoder
source .venv/bin/activate
MEOW_TEST_MODE=1 python web_demo/test_cat_e2e_speeds.pyThis test is only needed to verify the eye-blink video transmission feature works. For basic demo verification,
test_cat_mode.pyis sufficient.
# Start Flask app
cd web_demo
python app.py
# Visit http://localhost:5000 and:
# 1. Upload any file
# 2. Select Cat Mode
# 3. Set password "test123"
# 4. Click Encode
# 5. Download resulting APNG
# 6. Go to Decode page
# 7. Upload the APNG, enter password "test123"
# 8. Verify recovered file matches original- 1 KB file: ~1 second
- 100 KB file: ~3 seconds
- 1 MB file: ~10 seconds
- 8 MB file: ~60 seconds
Times vary based on:
- Argon2id key derivation (20 iterations, 512 MiB memory)
- Fountain encoding redundancy
- QR frame generation
- Server CPU speed
- Peak memory: ~200-500 MB per encoding operation
- Baseline: ~50 MB (Flask + dependencies)
For high-traffic deployments, consider:
- Nginx reverse proxy with request queuing
- Gunicorn with multiple workers
- Redis task queue (Celery) for async encoding
Meow Decoder is open source (see main project LICENSE file).
- Main Repository: https://github.com/systemslibrarian/meow-decoder
- Documentation: https://github.com/systemslibrarian/meow-decoder/blob/main/docs/
- Quick Start Guide: https://github.com/systemslibrarian/meow-decoder/blob/main/QUICKSTART.md
For issues specific to the web demo, please open an issue on GitHub with the web-demo label.
For general Meow Decoder questions, see the main project README.
π± Made with <3 and cats by the Meow Decoder team