Dev log Β· 2026-07-07

Hiding plastron links in pictures with ChaosEdgeSteg 🐒

ChaosEdgeSteg banner from the crypt0lith project
Banner from crypt0lith/ChaosEdgeSteg

plastron now ships a sheetapp that smuggles a plastron.ca/#f=… share link through an ordinary-looking PNG β€” edge-adaptive, chaos-keyed LSB steganography from crypt0lith/ChaosEdgeSteg, running verbatim as Python in the browser via Pyodide. Post the picture to X; your friend recovers the link with the original cover and password.

What is ChaosEdgeSteg?

ChaosEdgeSteg is a Python tool from the crypt0lith project β€” chaos-based, edge-adaptive LSB steganography. Instead of hiding bits in every pixel (which shows up under statistical analysis), it picks edge pixels via an adaptive Canny pass, orders them with a HΓ©non chaotic map keyed off your password, and writes the payload into the least significant bits of those locations only.

The scheme is non-blind and keyed: decoding needs three things β€” the original cover image, the steg image (what you downloaded from X), and the password you shared out of band. The cover reconstructs the edge pixel set; the password drives the HΓ©non ordering. Wrong cover, wrong image order, or wrong password fails fast with a bad password check on the CES header.

Upstream ships a CLI:

pip install git+https://github.com/crypt0lith/ChaosEdgeSteg.git

# embed files into a cover image
chaosedgesteg embed cover.png secret.txt -O steg.png

# extract from the steg image (needs the same cover + password)
chaosedgesteg extract cover.png steg.png -O extracted.zip

plastron's integration is byte-compatible both ways: a steg PNG made in the browser extracts under the desktop CLI, and an upstream-made steg image decodes in plastron.

Why this lives on plastron

The use case is specific: hide a https://plastron.ca/#f=<base64url> f-string link β€” a shareable deep link into a live sketch (boids, kanban, whatever) β€” inside a small PNG you can post to Twitter/X. If the image survives X's re-encode, anyone with the cover and password can pull the link back out and click straight into your sketch.

ChaosEdgeSteg is not a new plastron application segment. It is a sheetapp document β€” a workbook of positioned cells where the algorithm, UI, password field, size guard, and decode logic are all ordinary spreadsheet formulas and a single Python source cell. Open it from the desktop like any other doc.

plastron origin desktop with the ChaosEdgeSteg icon visible among other apps
The πŸ•΅οΈ ChaosEdgeSteg icon on the plastron origin desktop β€” opens doc:chaosedgesteg.

How Pyodide runs the upstream code

plastron already has a py compiler cel: paste Python, bind it with =def('name', 'py', sourceCell), call the minted function from formulas. For ChaosEdgeSteg we dropped the upstream henon.py and steg.py bodies verbatim into cell A21, plus thin glue for PNG↔BGR conversion and the upstream zip container. Cell A22 binds it: =def('chaosedgesteg.ces', 'py', A21).

The runtime is Pyodide v0.29.4 (CPython 3.13 in WebAssembly). On first open, the browser downloads Pyodide core plus the packages the source actually imports β€” numpy, opencv-python (cv2), pillow, mpmath β€” roughly 30 MB from jsDelivr, cached after the first run. PNG I/O goes through Pillow + numpy; OpenCV handles grayscale, bilateral filter, adaptive Canny, and LSB writes. There is no reimplementation of the chaotic-map math β€” parity is upstream's own bytes.

One small native change made this possible: the py compiler now calls pyodide.loadPackagesFromImports(source) before runPython, so any import line in a py cell auto-fetches its wheel. ChaosEdgeSteg is the worked example; before this, a cell that imported numpy would silently fail with ModuleNotFoundError.

The algorithm is policy in cells; the only native additions are package auto-loading and fs.pickToCel (turn a file-picker upload into base64 in a named cell). No new verbs for steganography itself.
ChaosEdgeSteg workbook open β€” worksheet on the left, Decode and Embed view panes on the right
The workbook layout: grid of cells on the left (including the visible Python source in A21), πŸ”“ Decode and πŸ–Ό Embed view panes on the right.

How to use it

You need three things to decode later: the original cover, the image from X, and the password. Keep the cover; share the password out of band.

1. Open ChaosEdgeSteg

Go to plastron.ca, open the origin desktop, and click πŸ•΅οΈ ChaosEdgeSteg. A workbook opens with two tabs on the right β€” πŸ”“ Decode and πŸ–Ό Embed.

2. Embed a link

  1. On the πŸ–Ό Embed pane, paste your plastron.ca/#f=… link (or leave the default boids link).
  2. Pick a PNG cover image. The sheet checks dimensions immediately.
  3. Set a password (default SECRET_PASSWORD, same as upstream).
  4. Wait for embedding β€” the first run loads OpenCV via Pyodide (a few seconds). When done, click ⬇ Download steg PNG.
Embed pane after embedding completes, showing the Download steg PNG button
After embedding: the steg PNG is ready to download. It looks like the same ordinary picture.

3. The ≀680px rule (critical for X)

We ran a real round-trip test: seven steg PNGs posted to X, downloaded, decoded. PNGs with max dimension ≀ 680px came back byte-for-byte identical β€” link recovered exactly. Images around 900px or larger were JPEG-recompressed and the LSBs were destroyed.

The Embed pane enforces this: if your cover is too big, you see TOO BIG: 1200Γ—630 … and the sheet auto-downscales to ≀680px before embedding. If a downscaled copy was used, a second download link offers the cover actually used β€” your decoder needs that exact file, not the original oversized one.

Embed pane showing size OK confirmation for a 680px cover
Size check passed: size OK β€” 680Γ—357, ≀680px. This cover will survive X's lossless re-encode.
Original cover PNG before steganography
Original cover
Steg PNG with hidden link β€” visually indistinguishable from cover
Steg PNG (after X round-trip) β€” looks identical

4. Post to X

Upload the downloaded steg PNG to X as-is β€” no crop, no filter. This step is outside plastron; the app deliberately does not fetch from twimg.com (CORS). Later, download the image X served back.

5. Decode

  1. On the πŸ”“ Decode pane, upload the original cover and the image from X.
  2. Enter the password.
  3. The recovered text appears β€” as a clickable link when it is a URL. The sheet tries cover/steg in both orderings automatically.
  4. Click the link; your boids sketch (or whatever you hid) opens.
Decode pane showing the recovered plastron.ca link as a clickable anchor
Decode complete: the hidden plastron.ca/#f=… link recovered character-for-character and rendered as a clickable anchor.

6. Cross-tool parity

A steg PNG made in plastron also opens in the upstream CLI:

chaosedgesteg extract cover.png plastron-steg.png -O recovered.zip

And the reverse: an image embedded with upstream's CLI decodes in the plastron workbook with the same cover and password. The zip container (comment b"0", member 0.bin) matches upstream's format exactly.

Under the hood (for the curious)

The embed path: grayscale the cover β†’ adaptive Canny (bisection over thresholds on a bilateral-filtered image to hit target edge density) β†’ HΓ©non-map ordering keyed by a BLAKE2b-8 hash of the password β†’ write LSBs at those pixels: a CES magic header plus 4-byte length, then payload bits. Extract re-runs Canny on the original cover to reconstruct the pixel set, reads LSBs from the carrier, checks the header.

Formulas wire it together without incantations:

Closing the workbook flushes and evicts every chaosedgesteg.* cell; the shared Pyodide runtime stays cached at process level for the next py cell that needs it.

Status

Shipped in plastron-examples/origin as apps/docs/chaosedgesteg.json, with a capstone e2e (e2e/chaosedgesteg.mjs) that embeds a boids link, decodes it, and verifies upstream parity on a fixture that actually round-tripped through X. A lighter sibling, πŸ•΅οΈ NanoSteg, offers dependency-free LSB hiding for contrast β€” different algorithm, incompatible by design.

This is a fun demonstration of plastron's "algorithm as spreadsheet policy" model: real scientific Python, visible in a cell, running in the browser, with every UX decision expressed as positioned formulas. Not a security product β€” steganography is obscurity, not encryption β€” but a neat way to smuggle share links through social image pipelines.

References

More from the dev log