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.txt
invjsible 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 -v
invjsible 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.txt
invjsible analyze <file> Example: invjsible analyze suspicious.txt
invjsible clean <file> [options] Options: -o, --output Output file (default: <file>.cleaned) Example: invjsible clean document.txt
invjsible list
Hide 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.encoded
Create 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 systems
Hide 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 characters
Add 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.html
The 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.decoded
This 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.decoded
Contributions 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!