Encode any file into invisible Unicode characters. Hide data in plain sight.
invjsible is a powerful CLI tool that encodes files using invisible Unicode characters (Zero-Width Spaces, Zero-Width Non-Joiners, etc.). The encoded files look completely blank but contain all the original data. Perfect for steganography, data hiding, or just having fun with invisible text.
- π Invisible Encoding: Encode any file into invisible Unicode characters
- π¦ Maximum Compression: Uses Brotli compression at maximum level (11)
- π Self-Extracting: Create runnable files that auto-extract and execute
- π Lossless: Perfect roundtrip encoding/decoding with no data loss
- π― Multi-Format: Works with text, binary, JavaScript, Python, Shell, Ruby files
- π Analysis Tools: Detect and analyze invisible characters in files
- π§Ή Cleanup: Remove invisible characters from contaminated files
- β‘ Fast: Optimized encoding/decoding algorithms
npm i -g invjsible# Clone the repository
git clone https://github.com/stringmanolo/invjsible.git
cd invjsible
# Make it executable (Unix/Linux/Mac)
chmod +x invjsible.js
# Create global symlink
npm link# Help menu
invjsible
# Encode a file in invisible characters
invjsible encode secret.txt
# Encode a file with compression (Recomended to always use)
invjsible encode secret.txt --compress
# Create a self-extracting executable
invjsible encode app.js --compress --runable
# Run the encoded file
node app.js.encoded
# or ./app.js.encoded
# Decode the file
invjsible decode secret.txt.encoded
# Analyze invisible characters
invjsible analyze suspicious.txtinvjsible encode <file> [options]
Options:
--compress Compare direct vs compressed encoding, use smaller
--runable Generate self-extracting executable
-o, --output Output file (default: <file>.encoded)
-v, --verbose Show detailed information
Examples:
invjsible encode document.txt
invjsible encode document.txt --compress
invjsible encode script.js --runable
invjsible encode app.js --compress --runable -vinvjsible decode <file> [options]
Options:
-o, --output Output file (default: <file>.decoded)
-v, --verbose Show detailed information
Examples:
invjsible decode document.txt.encoded
invjsible decode encoded.txt -o original.txtinvjsible analyze <file>
Example:
invjsible analyze suspicious.txtinvjsible clean <file> [options]
Options:
-o, --output Output file (default: <file>.cleaned)
Example:
invjsible clean document.txtinvjsible listHide secret messages in plain sight:
# Encode a secret message
echo "Secret data" > secret.txt
invjsible encode secret.txt
# The .encoded file looks blank but contains the data
cat secret.txt.encoded # Appears empty!
# Decode to recover
invjsible decode secret.txt.encodedCreate files that execute themselves:
# Encode a Node.js application
invjsible encode server.js --compress --runable
# The encoded file is executable
node server.js.encoded
# or
./server.js.encoded # On Unix systemsHide data in text documents:
# Encode binary data
invjsible encode image.png
# Paste the encoded content anywhere in a text document
# The binary data is preserved as invisible charactersAdd invisible watermarks to text:
# Encode watermark data
echo "Copyright StringManolo 2025" >> copy.txt && invjsible encode copy.txt
# Append to any file
cat copy.txt.encoded >> myDocument.txt
# The watermark is invisible but recoverable- Read File: Load the original file into memory
- Compress (Optional): Apply Brotli compression at maximum level
- Binary Encoding: Convert each byte to 8-bit binary representation
- Invisible Mapping:
0β Zero-Width Space (U+200B)1β Zero-Width Non-Joiner (U+200C)
- Marker Addition: Add compression marker if compressed
- Save: Write the invisible character string to file
When you use --compress, invjsible automatically compares two methods and chooses the smaller result:
Option 1: Direct Encode
- Encode directly without compression
- Faster encoding
Option 2: Compress β Encode
- Compress first with Brotli, then encode
- Best for most files, especially repetitive content
- Marked with Zero-Width Joiner (
U+200D)
The tool automatically selects the most efficient method, ensuring you always get the smallest possible output.
- Detect Format: Check for compression marker
- Decompress (if needed): Apply decompression if marker is present
- Binary Decoding: Convert invisible characters back to binary
- Reconstruct: Rebuild the original file byte by byte
Typical compression ratios with --compress:
| File Type | Original Size | Encoded Size | Ratio |
|---|---|---|---|
| Text (repetitive) | 100 KB | ~15 KB | 15% |
| Text (random) | 100 KB | ~30 KB | 30% |
| JavaScript | 100 KB | ~20 KB | 20% |
| Binary (PNG) | 100 KB | ~40 KB | 40% |
| Already Compressed | 100 KB | ~80 KB | 80% |
Note: Files already compressed (PNG, ZIP, etc.) don't compress well.
# Install Jest
npm install --save-dev jest
# Run all tests
npm test
# Run with coverage report
npm run test:coverage
# Watch mode for development
npm run test:watch
# Generate HTML coverage report
npm test -- --coverage --coverageReporters=html
# Then open: coverage/index.htmlThe project has exceptional test coverage with 172 comprehensive tests:
Statements : 99.67% (305/306)
Branches : 94.59% (140/148)
Functions : 100% (19/19)
Lines : 99.66% (294/295)
β Core Functionality
- Encoding/decoding with invisible characters
- Compression vs non-compression selection
- Binary encoding (all byte values 0x00-0xFF)
β CLI Commands
- All commands:
encode,decode,analyze,clean,list,help - All flags:
--compress,--runable,--verbose,-o,--output - Error handling for all commands
β Runnable Templates
- Self-extracting executables for:
.js,.mjs,.sh,.bash,.py,.rb,.txt - Files without extensions
- Compression in runnable mode
β File Types
- Text files (ASCII, UTF-8, Unicode)
- Binary files (images, executables)
- Empty files
- Files with special characters and emojis
- Files with null bytes
β Edge Cases
- Empty buffers and files
- Incomplete bytes (7 bits)
- All byte patterns (0x00, 0xFF, 0x55, 0xAA, sequential)
- Maximum compression scenarios
- Files with 20, 21, 50+ invisible characters
- Very long filenames (200+ characters)
- Corrupted compression data
β Integration Tests
- Complete encode/decode roundtrips
- Preservation of binary data integrity
- Runnable file execution and extraction
- CLI end-to-end workflows
β Performance Tests
- Large file encoding (< 5 seconds)
- Large file decoding (< 5 seconds)
- Compression efficiency verification
β Platform Compatibility
- Unix/Linux/Mac permissions (chmod)
- Windows graceful error handling
- Cross-platform file operations
| Character | Unicode | Code | Usage |
|---|---|---|---|
| Zero-Width Space | U+200B | 8203 | Binary 0 |
| Zero-Width Non-Joiner | U+200C | 8204 | Binary 1 |
| Zero-Width Joiner | U+200D | 8205 | Compression marker |
[Optional: 1-byte compression marker (U+200D)]
[Invisible character string representing binary data]
#!/usr/bin/env node
// Self-extracting executable generated by invjsible
[Minified decoder + embedded invisible data]You can also use invjsible as a module:
const { encode, decode, encodeToInvisible, decodeFromInvisible } = require('./invjsible.js');
// Encode a buffer
const buffer = Buffer.from('Hello World');
const invisible = encodeToInvisible(buffer);
console.log(invisible); // Invisible characters
// Decode back
const { buffer: decoded } = decodeFromInvisible(invisible);
console.log(decoded.toString()); // "Hello World"
// Encode a file
await encode('input.txt', 'output.encoded', {
compress: true,
runable: false,
verbose: true
});
// Decode a file
await decode('output.encoded', 'output.decoded', {
verbose: true
});- Not Encryption: This is encoding, not encryption. Data is not secure.
- Obfuscation Only: Provides obscurity, not cryptographic security.
- Steganography: Good for hiding data in plain sight.
- No Authentication: No way to verify data integrity or authenticity.
For actual security, combine with encryption tools like gpg:
# Encrypt then encode
gpg --encrypt secret.txt
invjsible encode secret.txt.gpg
# Decode then decrypt
invjsible decode secret.txt.gpg.encoded
gpg --decrypt secret.txt.gpg.decodedThis is an example of hidding a message into a html file and recovering it.
- Create the html file
echo '<!DOCTYPE html>
<html lang="en">
<head prefix="og:http://ogp.me/ns#">
<meta charset="utf-8">
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
<title>Hello World</title>
</head>
<body><!-- This html file contains hidden data -->
<div id="myApp"></div>
<script>
const myApp = document.querySelector("#myApp");
myApp.innerText="Hello World!"
</script>
</body>
</html>' > myIndex.html- Check the html file has no hidden data:
cat myIndex.html | xxd00000000: 3c21 444f 4354 5950 4520 6874 6d6c 3e0a <!DOCTYPE html>.
00000010: 3c68 746d 6c20 6c61 6e67 3d22 656e 223e <html lang="en">
00000020: 0a3c 6865 6164 2070 7265 6669 783d 226f .<head prefix="o
00000030: 673a 6874 7470 3a2f 2f6f 6770 2e6d 652f g:http://ogp.me/
00000040: 6e73 2322 3e0a 2020 3c6d 6574 6120 6368 ns#">. <meta ch
00000050: 6172 7365 743d 2275 7466 2d38 223e 0a20 arset="utf-8">.
00000060: 203c 6c69 6e6b 2072 656c 3d22 6963 6f6e <link rel="icon
00000070: 2220 6872 6566 3d22 6461 7461 3a3b 6261 " href="data:;ba
00000080: 7365 3634 2c69 5642 4f52 7730 4b47 676f se64,iVBORw0KGgo
00000090: 3d22 3e0a 2020 3c74 6974 6c65 3e48 656c =">. <title>Hel
000000a0: 6c6f 2057 6f72 6c64 3c2f 7469 746c 653e lo World</title>
000000b0: 0a3c 2f68 6561 643e 0a3c 626f 6479 3e3c .</head>.<body><
000000c0: 212d 2d20 5468 6973 2068 746d 6c20 6669 !-- This html fi
000000d0: 6c65 2063 6f6e 7461 696e 7320 6869 6464 le contains hidd
000000e0: 656e 2064 6174 6120 2d2d 3e0a 2020 3c64 en data -->. <d
000000f0: 6976 2069 643d 226d 7941 7070 223e 3c2f iv id="myApp"></
00000100: 6469 763e 0a20 203c 7363 7269 7074 3e0a div>. <script>.
00000110: 2020 636f 6e73 7420 6d79 4170 7020 3d20 const myApp =
00000120: 646f 6375 6d65 6e74 2e71 7565 7279 5365 document.querySe
00000130: 6c65 6374 6f72 2822 236d 7941 7070 2229 lector("#myApp")
00000140: 3b0a 2020 6d79 4170 702e 696e 6e65 7254 ;. myApp.innerT
00000150: 6578 743d 2248 656c 6c6f 2057 6f72 6c64 ext="Hello World
00000160: 2122 0a20 203c 2f73 6372 6970 743e 0a3c !". </script>.<
00000170: 2f62 6f64 793e 0a3c 2f68 746d 6c3e 0a /body>.</html>.- Create a hidden message (you can reaname any file to secret.txt)
echo "I'm a secret message" > secret.txt- Encode the message
invjsible encode secret.txt- Split the html in half by the line you want (9 in my case)
head -n 9 myIndex.html > half1.html
tail -n +10 myIndex.html > half2.html- Create the file with the hidden message:
cat half1.html secret.txt.encoded half2.html > index.html- Check the file has the hidden data
cat index.html | xxd00000000: 3c21 444f 4354 5950 4520 6874 6d6c 3e0a <!DOCTYPE html>.
00000010: 3c68 746d 6c20 6c61 6e67 3d22 656e 223e <html lang="en">
00000020: 0a20 203c 6865 6164 2070 7265 6669 783d . <head prefix=
00000030: 226f 673a 6874 7470 3a2f 2f6f 6770 2e6d "og:http://ogp.m
00000040: 652f 6e73 2322 3e0a 2020 3c6d 6574 6120 e/ns#">. <meta
00000050: 6368 6172 7365 743d 2275 7466 2d38 223e charset="utf-8">
00000060: 0a20 2020 203c 6c69 6e6b 2072 656c 3d22 . <link rel="
00000070: 6963 6f6e 2220 6872 6566 3d22 6461 7461 icon" href="data
00000080: 3a3b 6261 7365 3634 2c69 5642 4f52 7730 :;base64,iVBORw0
00000090: 4b47 676f 3d22 3e0a 2020 3c74 6974 6c65 KGgo=">. <title
000000a0: 3e48 656c 6c6f 2057 6f72 6c64 3c2f 7469 >Hello World</ti
000000b0: 746c 653e 0a3c 2f68 6561 643e 0a3c 626f tle>.</head>.<bo
000000c0: 6479 3e3c 212d 2d20 5468 6973 2068 746d dy><!-- This htm
000000d0: 6c20 6669 6c65 2063 6f6e 7461 696e 7320 l file contains
000000e0: 6869 6464 656e 2064 6174 6120 2d2d 3e0a hidden data -->.
000000f0: 2020 3c64 6976 2069 643d 226d 7941 7070 <div id="myApp
00000100: 223e 3c2f 6469 763e 0ae2 808b e280 8ce2 "></div>........
00000110: 808b e280 8be2 808c e280 8be2 808b e280 ................
00000120: 8ce2 808b e280 8be2 808c e280 8be2 808b ................
00000130: e280 8ce2 808c e280 8ce2 808b e280 8ce2 ................
00000140: 808c e280 8be2 808c e280 8ce2 808b e280 ................
00000150: 8ce2 808b e280 8be2 808c e280 8be2 808b ................
00000160: e280 8be2 808b e280 8be2 808b e280 8ce2 ................
00000170: 808c e280 8be2 808b e280 8be2 808b e280 ................
00000180: 8ce2 808b e280 8be2 808c e280 8be2 808b ................
00000190: e280 8be2 808b e280 8be2 808b e280 8ce2 ................
000001a0: 808c e280 8ce2 808b e280 8be2 808c e280 ................
000001b0: 8ce2 808b e280 8ce2 808c e280 8be2 808b ................
000001c0: e280 8ce2 808b e280 8ce2 808b e280 8ce2 ................
000001d0: 808c e280 8be2 808b e280 8be2 808c e280 ................
000001e0: 8ce2 808b e280 8ce2 808c e280 8ce2 808b ................
000001f0: e280 8be2 808c e280 8be2 808b e280 8ce2 ................
00000200: 808c e280 8be2 808b e280 8ce2 808b e280 ................
00000210: 8ce2 808b e280 8ce2 808c e280 8ce2 808b ................
00000220: e280 8ce2 808b e280 8be2 808b e280 8be2 ................
00000230: 808c e280 8be2 808b e280 8be2 808b e280 ................
00000240: 8be2 808b e280 8ce2 808c e280 8be2 808c ................
00000250: e280 8ce2 808b e280 8ce2 808b e280 8ce2 ................
00000260: 808c e280 8be2 808b e280 8ce2 808b e280 ................
00000270: 8ce2 808b e280 8ce2 808c e280 8ce2 808b ................
00000280: e280 8be2 808c e280 8ce2 808b e280 8ce2 ................
00000290: 808c e280 8ce2 808b e280 8be2 808c e280 ................
000002a0: 8ce2 808b e280 8ce2 808c e280 8be2 808b ................
000002b0: e280 8be2 808b e280 8ce2 808b e280 8ce2 ................
000002c0: 808c e280 8be2 808b e280 8ce2 808c e280 ................
000002d0: 8ce2 808b e280 8ce2 808c e280 8be2 808b ................
000002e0: e280 8ce2 808b e280 8ce2 808b e280 8be2 ................
000002f0: 808b e280 8be2 808c e280 8be2 808c e280 ................
00000300: 8b20 203c 7363 7269 7074 3e0a 2020 636f . <script>. co
00000310: 6e73 7420 6d79 4170 7020 3d20 646f 6375 nst myApp = docu
00000320: 6d65 6e74 2e71 7565 7279 5365 6c65 6374 ment.querySelect
00000330: 6f72 2822 236d 7941 7070 2229 3b0a 2020 or("#myApp");.
00000340: 6d79 4170 702e 696e 6e65 7254 6578 743d myApp.innerText=
00000350: 2248 656c 6c6f 2057 6f72 6c64 2122 0a20 "Hello World!".
00000360: 203c 2f73 6372 6970 743e 0a3c 2f62 6f64 </script>.</bod
00000370: 793e 0a3c 2f68 746d 6c3e 0a y>.</html>.Chrome's view-source: cat, curl and other commands will not show the hidden data.
- To decode, split the index.html in half, get the line with encoded data and decode it
# 1. Get line number 10 directly
sed -n '10p' index.html > secret.txt.encoded
# 2. Decode and print it.
invjsible decode secret.txt.encoded && cat secret.txt.decoded
# Using a single line:
sed -n '10p' index.html > secret.txt.encoded && invjsible decode secret.txt.encoded && cat secret.txt.decodedContributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
This project is licensed under the GPLV3 License - see the LICENSE file for details.
If you find this project useful, please consider giving it a β on GitHub!