stegdoc 5.7.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,200 +1,325 @@
1
1
  # stegdoc
2
2
 
3
- > Hide files inside Office documents with AES-256 encryption and steganography
3
+ > Hide a file inside a legitimate-looking Office document
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/stegdoc.svg)](https://www.npmjs.com/package/stegdoc)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
 
8
- **stegdoc** is a CLI tool that encodes any file into legitimate-looking Office documents (Excel/Word). Your data is hidden within realistic server access logs and Hebrew incident reports — no hidden sheets, no suspicious content. Optionally protected with AES-256-GCM encryption.
8
+ **stegdoc** encodes any file into an ordinary-looking Excel or Word document.
9
+ The payload is spread across realistic nginx access log entries or Hebrew
10
+ incident reports, so the cover survives casual inspection. Optionally protected
11
+ with AES-256-GCM.
12
+
13
+ The format engine is written in Rust. It ships as a prebuilt native addon for
14
+ the CLI, and as WebAssembly for the single-file browser tool.
9
15
 
10
16
  ## Features
11
17
 
12
- - **Log-Based Steganography** — Payload is embedded directly in realistic nginx access log entries (URL tokens, UUIDs, trace IDs). No hidden sheets — the data IS the logs
13
- - **AES-256-GCM Encryption** Military-grade encryption with PBKDF2 key derivation (100k iterations)
14
- - **Brotli Compression** 15-25% better compression than gzip
15
- - **Dual Format Support**
16
- - **XLSX** Access log spreadsheet with realistic entries, any file size
17
- - **DOCX** Hebrew RTL incident report with log excerpts, files under 1 MB
18
- - **Multi-part Splitting** — Automatically split large files across multiple documents
19
- - **Integrity Verification** — SHA-256 hashing detects tampering
20
- - **Folder Support** — Encode entire directories (automatically zipped)
21
- - **Interactive Mode** User-friendly prompts guide you through options
22
- - **Legacy Compatibility** `--legacy` flag produces v4 format for older environments
23
- - **Backward Compatible** Reads and decodes all previous format versions
24
-
25
- ## Installation
18
+ - **Log-based steganography** — the data *is* the logs; there is no hidden
19
+ sheet and nothing extra to notice
20
+ - **AES-256-GCM** encryption, password-derived keys
21
+ - **v6 by default** — Argon2id key derivation and tamper-evident metadata, with
22
+ `--v5` for older decoders
23
+ - **Brotli** compression (quality 6), skipped for formats that are already
24
+ compressed
25
+ - **XLSX and DOCX** covers an access-log spreadsheet or a Hebrew RTL
26
+ incident report
27
+ - **Multi-part splitting** for payloads larger than one document
28
+ - **Integrity checking** with SHA-256, verified on decode
29
+ - **ZIP, directory, or single-file input** on decode, discovered automatically
30
+ - **Backward compatible** — the decoder reads every earlier format version
31
+
32
+ ## Install
26
33
 
27
34
  ```bash
28
35
  npm install -g stegdoc
29
36
  ```
30
37
 
31
- Or run directly with npx:
38
+ The install pulls a prebuilt engine for the host platform (`win32-x64`,
39
+ `linux-x64`, `linux-arm64`, `darwin-x64`, `darwin-arm64`). No compiler or Rust
40
+ toolchain is required. If a platform package is missing, log-embed files cannot
41
+ be decoded; the CLI says so instead of guessing.
42
+
43
+ Or run it without installing:
32
44
 
33
45
  ```bash
34
- npx stegdoc encode myfile.pdf
46
+ npx stegdoc encode myfile.pdf -p mypassword
35
47
  ```
36
48
 
37
- ## Quick Start
49
+ ## Quick start
38
50
 
39
51
  ```bash
40
- # Encode a file with encryption (recommended)
52
+ # Hide a file, encrypted
41
53
  stegdoc encode secret.pdf -p mypassword
42
54
 
43
- # Decode it back
55
+ # Get it back
44
56
  stegdoc decode access_log_20260315_1200_A1B2_part1.xlsx -p mypassword
45
57
 
46
- # View file info without decoding
58
+ # Inspect without decoding
47
59
  stegdoc info access_log_20260315_1200_A1B2_part1.xlsx
48
60
 
49
- # Verify file integrity
61
+ # Check that it will decode
50
62
  stegdoc verify access_log_20260315_1200_A1B2_part1.xlsx -p mypassword
51
63
  ```
52
64
 
53
65
  ## Commands
54
66
 
55
- ### `encode` — Hide a file in an Office document
56
-
57
- ```bash
58
- stegdoc encode <file> [options]
59
- ```
67
+ ### `encode <inputs...>`
60
68
 
61
69
  | Option | Description | Default |
62
70
  |--------|-------------|---------|
63
71
  | `-o, --output-dir <dir>` | Output directory | Current directory |
64
- | `-s, --chunk-size <size>` | Split size: `5MB`, `25MB`, `3 parts`, `max` | `5MB` |
65
- | `-f, --format <format>` | Output format: `xlsx` or `docx` | `xlsx` |
72
+ | `--bundle-name <name>` | Filename recorded when several inputs are bundled | `bundle.zip` |
73
+ | `-s, --chunk-size <size>` | Split size: `5MB`, `25MB`, `3 parts`, or `max` | `5MB` |
74
+ | `-f, --format <format>` | Cover format: `xlsx` or `docx` | `xlsx` |
66
75
  | `-p, --password <pass>` | Encryption password | None (unencrypted) |
67
- | `--legacy` | Use v4 format for backward compatibility | Off |
68
- | `--no-limit` | Bypass DOCX 1 MB size limit | Off |
76
+ | `--v5` | Emit the v5 format (PBKDF2) | Off (v6) |
77
+ | `--v6` | Emit the v6 format (the default) | On |
78
+ | `--legacy` | Emit the legacy v4 DOCX format | Off |
79
+ | `--no-limit` | Bypass the DOCX 1 MB limit | Off |
69
80
  | `--force` | Overwrite existing files | Prompt |
70
81
  | `-q, --quiet` | Minimal output for scripting | Off |
71
82
  | `-y, --yes` | Skip interactive prompts | Off |
72
83
 
73
- **Examples:**
84
+ A directory is zipped before encoding, and several inputs are bundled into one
85
+ zip first, so a decode hands that archive back. `-s max` produces a single part;
86
+ `-s "3 parts"` splits into roughly three.
74
87
 
75
88
  ```bash
76
- # Encode with password (produces access log spreadsheet)
77
- stegdoc encode document.pdf -p mysecret
78
-
79
- # Encode as Hebrew incident report (DOCX)
80
- stegdoc encode config.json -p mysecret -f docx
81
-
82
- # Split into 3 parts
83
- stegdoc encode large-file.zip -p mysecret -s "3 parts"
84
-
85
- # Legacy v4 format (for older environments)
86
- stegdoc encode data.bin -p mysecret --legacy
87
-
88
- # Encode a folder
89
- stegdoc encode ./my-folder -p mysecret
89
+ stegdoc encode document.pdf -p mysecret # access log spreadsheet
90
+ stegdoc encode config.json -p mysecret -f docx # Hebrew incident report
91
+ stegdoc encode large-file.zip -p mysecret -s "3 parts" # three parts
92
+ stegdoc encode ./my-folder -p mysecret # zipped first
93
+ stegdoc encode a.pdf b.pdf --bundle-name pair.zip -p mysecret
90
94
  ```
91
95
 
92
- ### `decode` — Recover the original file
93
-
94
- ```bash
95
- stegdoc decode <file> [options]
96
- ```
96
+ ### `decode <file>`
97
97
 
98
98
  | Option | Description | Default |
99
99
  |--------|-------------|---------|
100
- | `-o, --output <path>` | Output file path | Original filename |
100
+ | `-o, --output <path>` | Output file or directory | Original filename |
101
101
  | `-p, --password <pass>` | Decryption password | Prompt if needed |
102
102
  | `--force` | Overwrite existing files | Prompt |
103
103
  | `-q, --quiet` | Minimal output | Off |
104
- | `-y, --yes` | Skip prompts, fail if password needed | Off |
104
+ | `-y, --yes` | Skip prompts, fail if a password is needed | Off |
105
105
 
106
- **Examples:**
106
+ Point `decode` at any one part, a directory holding the parts, or a ZIP of
107
+ them; the rest are found automatically.
107
108
 
108
109
  ```bash
109
- # Decode with password
110
110
  stegdoc decode access_log_20260315_1200_A1B2_part1.xlsx -p mysecret
111
-
112
- # Decode DOCX
113
111
  stegdoc decode system_report_20260315_0800_CD42_part1.docx -p mysecret
114
-
115
- # Multi-part files auto-detected (just provide part 1)
116
- stegdoc decode access_log_20260315_1200_A1B2_part1.xlsx -p mysecret
112
+ stegdoc decode stegdoc-parts.zip -p mysecret -o ./restored
117
113
  ```
118
114
 
119
- ### `info` — View metadata without decoding
115
+ ### `info <file>`
120
116
 
121
- ```bash
122
- stegdoc info <file>
123
- ```
117
+ Reads metadata without decoding: cover format and version, original filename
118
+ and size, whether it is encrypted and compressed, the part count, and whether
119
+ metadata is authenticated.
124
120
 
125
- ### `verify` Validate file integrity
121
+ ### `verify <file> [-p <pass>]`
126
122
 
127
- ```bash
128
- stegdoc verify <file> [-p <password>]
129
- ```
123
+ Runs the structural and password checks without writing any output. It exits
124
+ non-zero if the file will not decode.
130
125
 
131
- ## How It Works
126
+ ## How it works
132
127
 
133
- ### v5 Pipeline (default)
128
+ ### Pipeline
134
129
 
135
130
  ```
136
- Input File
137
- |
138
- [Brotli Compression]
139
- |
140
- [AES-256-GCM Encryption] (optional)
141
- |
142
- [Log-Embed Encoding] -- payload distributed across log line fields
143
- |
144
- [Office Wrapper] -- XLSX access logs or DOCX incident report
145
- |
146
- Output File(s)
131
+ Input
132
+ |
133
+ [Brotli q6] skipped for already-compressed types
134
+ |
135
+ [AES-256-GCM] optional
136
+ |
137
+ [log-embed] payload spread across log line fields
138
+ |
139
+ [Office wrapper] XLSX access logs or a DOCX incident report
140
+ |
141
+ Output part(s)
147
142
  ```
148
143
 
149
- ### Data Channels (per log line)
150
-
151
- Each nginx access log entry carries **114 bytes** of payload across 6 channels:
144
+ Each log row carries **114 raw bytes** across six channels:
152
145
 
153
- | Channel | Format | Bytes |
154
- |---------|--------|-------|
146
+ | Channel | Encoding | Bytes |
147
+ |---------|----------|-------|
155
148
  | URL path segment | base64url | 21 |
156
149
  | Query param `token` | base64url | 21 |
157
150
  | Query param `state` | base64url | 21 |
158
151
  | Referer `ref` param | base64url | 21 |
159
- | X-Request-ID | UUID v4 (hex) | 14 |
160
- | X-Trace-ID | 32-char hex | 16 |
152
+ | `X-Request-ID` | UUID v4 | 14 |
153
+ | `X-Trace-ID` | 32-char hex | 16 |
154
+
155
+ Metadata and encryption parameters travel in header lines marked by the
156
+ `/api/v1/health` path, in a length-prefixed frame:
157
+
158
+ ```
159
+ STGD06|<metaLen>|<encLen>|<kdfLen>|{metadataJson}{encryptionMeta}{kdfParams}
160
+ ```
161
+
162
+ v5 uses the shorter `STGD05|<metaLen>|<encLen>|...` frame. The decoder
163
+ dispatches on the marker.
161
164
 
162
- ### Output Formats
165
+ Decoding uses `payloadSize` and `dataLineCount` from the metadata to truncate
166
+ the payload. Filler rows are appended for realism and ignored on decode.
163
167
 
164
- **XLSX** — Single "Access Logs" sheet with realistic nginx log entries. Columns: Remote Address, Timestamp, Method, Request, Status, Bytes, Referer, User-Agent, X-Request-ID, X-Trace-ID. No hidden sheets.
168
+ ### Covers
165
169
 
166
- **DOCX** — Hebrew RTL incident report with title, executive summary, timeline table, log excerpts in monospace code blocks, root cause analysis, and recommendations. Reports are procedurally generated (15 services x 12 incident types = thousands of unique variants).
170
+ **XLSX** — a single "Access Logs" sheet of realistic nginx entries with columns
171
+ for remote address, timestamp, method, request, status, bytes, referer,
172
+ user-agent, `X-Request-ID`, and `X-Trace-ID`. No hidden sheets.
167
173
 
168
- ### Encryption
174
+ **DOCX** — a Hebrew RTL incident report: title, executive summary, timeline
175
+ table, log excerpts in monospace blocks, root cause, and recommendations. The
176
+ prose is generated deterministically from the payload hash, so covers do not
177
+ repeat. DOCX refuses inputs over 1 MB unless `--no-limit` is given; use XLSX
178
+ for large payloads.
169
179
 
170
- - **Algorithm**: AES-256-GCM (Galois/Counter Mode)
171
- - **Key Derivation**: PBKDF2-SHA256 with 100,000 iterations
172
- - **Key Size**: 256 bits
173
- - **IV**: 96 bits (randomly generated per part)
174
- - **Salt**: 128 bits (shared per session)
175
- - **Authentication**: 128-bit auth tag
180
+ ### Multi-part output
176
181
 
177
- ### Filenames
182
+ Large payloads are split across parts. Parts are built in parallel, each with
183
+ its own IV and tag but a shared session salt, and the CLI prints progress.
184
+ Decode finds the sibling parts automatically.
185
+
186
+ Filenames are decoys:
178
187
 
179
188
  ```
180
189
  access_log_YYYYMMDD_HH00_XXXX[_partN].xlsx
181
190
  system_report_YYYYMMDD_HH00_XXXX[_partN].docx
182
191
  ```
183
192
 
184
- ## Legacy Mode
193
+ ## Encryption
194
+
195
+ Encryption is optional but recommended. Without `-p`, the payload is hidden but
196
+ not encrypted.
197
+
198
+ ### v6 (default)
199
+
200
+ v6 makes the metadata tamper-evident and uses a memory-hard KDF. The cover,
201
+ channels, chunking, and filenames are identical to v5; only the header and
202
+ crypto change.
203
+
204
+ | | |
205
+ |---|---|
206
+ | Cipher | AES-256-GCM |
207
+ | Key derivation | Argon2id, version 19, `m=65536` KiB, `t=3`, `p=1` |
208
+ | Key | 256-bit |
209
+ | IV | 96-bit, random per part |
210
+ | Salt | 128-bit, shared per session |
211
+ | Tag | 128-bit |
212
+
213
+ The KDF parameters are recorded in the file and reproduced on decode. When
214
+ encrypted, the AEAD associated data is the header frame minus the encryption
215
+ parameters, so changing the original filename, size, content hash, or KDF
216
+ parameters breaks decryption. The frame is
217
+ `STGD06|<metaLen>|<encLen>|<kdfLen>|...`.
218
+
219
+ ### v5 (opt-in, `--v5`)
220
+
221
+ v5 uses PBKDF2 and leaves the metadata unauthenticated. It exists for decoders
222
+ that predate v6, and its read compatibility is permanent.
223
+
224
+ | | |
225
+ |---|---|
226
+ | Cipher | AES-256-GCM |
227
+ | Key derivation | PBKDF2-SHA256, 100,000 iterations |
228
+ | Key | 256-bit |
229
+ | IV | 96-bit, random per part |
230
+ | Salt | 128-bit, shared per session |
231
+ | Tag | 128-bit |
232
+
233
+ v6 requires the native engine to produce; `--v5` is the only format the
234
+ JavaScript fallback can emit. The decoder reads both versions without a flag.
235
+
236
+ ## Decode inputs
237
+
238
+ `decode` accepts:
239
+
240
+ - a single encoded `.xlsx`/`.docx`,
241
+ - a directory containing the sibling parts,
242
+ - a ZIP holding the parts.
243
+
244
+ A ZIP is expanded, non-container entries are ignored, and the first complete
245
+ part set is decoded. Only ZIP is supported; RAR and 7z would need extra
246
+ decoders.
247
+
248
+ ## Browser tool
249
+
250
+ The same engine also runs as WebAssembly in a single self-contained HTML file,
251
+ for offline use where installing anything is not an option.
252
+
253
+ ```bash
254
+ pnpm install
255
+ pnpm build:web # -> dist/stegdoc.html
256
+ ```
185
257
 
186
- Use `--legacy` to produce v4 format files (hidden sheet + gzip compression) for environments that haven't upgraded to stegdoc v5:
258
+ Open `dist/stegdoc.html` in a browser. Encoding and decoding run in a Web Worker
259
+ so the page stays responsive; nothing is uploaded and no network is used. The UI
260
+ offers XLSX or DOCX, v6 or v5, an optional password, an optional chunk size, and
261
+ Brotli compression. Several files can be selected at once and are bundled into
262
+ one zip before encoding. When the browser supports the File System Access API,
263
+ the page asks for an output folder and writes the documents straight into it;
264
+ otherwise a single part downloads directly and a multi-part set downloads as one
265
+ `stegdoc-parts.zip`. Input is held in memory, so the UI warns above 64 MiB and
266
+ refuses past 128 MiB with a pointer to the CLI.
267
+
268
+ ## Security model
269
+
270
+ - The native engine performs no filesystem writes. The CLI owns the output
271
+ path, the overwrite prompt, and the write.
272
+ - The original filename in the metadata is untrusted. It is reduced to a bare
273
+ filename and cannot escape the chosen output directory.
274
+ - Decompression output and ZIP expansion are bounded, so a crafted file fails
275
+ on the cap instead of exhausting memory or disk.
276
+ - `contentHash` is verified after decoding and the output is deleted on
277
+ mismatch.
278
+ - v6 binds the metadata to the ciphertext; v5 metadata is plaintext and its
279
+ integrity rests on `contentHash` and the path rules.
280
+
281
+ The wire format is specified in [`spec/FORMAT.md`](spec/FORMAT.md).
282
+
283
+ ## Compatibility
284
+
285
+ - The decoder auto-detects the version, so no flag is needed to read old files.
286
+ - v6 is the default output. `--v5` emits the older format; both cover XLSX and
287
+ DOCX. `--legacy` produces v4 DOCX only; legacy XLSX is unsupported.
288
+ - v3/v4 are read-only paths kept for old files.
289
+ - Log-embed (v5/v6) decoding requires the native engine. Without it, the CLI
290
+ fails loudly rather than falling back to an incomplete reader.
291
+ - Encoding and decoding are randomised, so two encodes of the same input are
292
+ not byte-identical; conformance is defined on the decode direction.
293
+
294
+ ## Building from source
295
+
296
+ Requires Node.js >= 18 and a Rust toolchain.
187
297
 
188
298
  ```bash
189
- stegdoc encode data.bin -p mypass --legacy
299
+ pnpm install
300
+ pnpm build:native # build the napi binding -> native/stegdoc.node
190
301
  ```
191
302
 
192
- The decoder auto-detects format version it reads both v4 and v5 files without any flags.
303
+ Then run the CLI directly with `node src/index.js <command>`.
304
+
305
+ | Command | Purpose |
306
+ |---------|---------|
307
+ | `pnpm test` | conformance against the frozen fixtures (builds native first) |
308
+ | `pnpm test:rust` | the Rust decoder against the same fixtures |
309
+ | `pnpm test:interop` | Rust encode -> JavaScript decode |
310
+ | `pnpm test:safety` | output-path traversal containment |
311
+ | `pnpm test:contract` | CLI flags, exit codes, filenames |
312
+ | `pnpm build:wasm` | the wasm binding -> `web/pkg` |
313
+ | `pnpm build:web` | the single-file browser tool -> `dist/stegdoc.html` |
314
+ | `pnpm test:wasm` | wasm round-trip, headless in Node |
315
+ | `pnpm test:browser` | the built page, round-trip from `file://` |
316
+ | `cargo test` | Rust unit and property tests |
193
317
 
194
318
  ## Requirements
195
319
 
196
- - Node.js 18.0.0 or higher
320
+ - Node.js >= 18 for the CLI
321
+ - A supported platform for the prebuilt engine (see Install)
197
322
 
198
323
  ## License
199
324
 
200
- MIT License — see [LICENSE](LICENSE) for details.
325
+ MIT — see [LICENSE](LICENSE).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stegdoc",
3
- "version": "5.7.0",
3
+ "version": "6.0.0",
4
4
  "description": "Hide files inside Office documents (XLSX/DOCX) with AES-256 encryption and steganography",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -9,7 +9,20 @@
9
9
  "scripts": {
10
10
  "start": "node src/index.js",
11
11
  "dev": "node src/index.js",
12
- "test": "echo \"Error: no test specified\" && exit 1"
12
+ "fixtures": "node spec/tools/generate-fixtures.js",
13
+ "vectors": "node spec/tools/generate-channel-vectors.js",
14
+ "build:native": "node scripts/build-native.js",
15
+ "build:binding": "node scripts/build-binding.js",
16
+ "prepare-publish": "node scripts/prepare-publish.js",
17
+ "build:wasm": "node scripts/build-wasm.js",
18
+ "build:web": "node scripts/build-web.js",
19
+ "test": "pnpm build:native && node spec/tools/conformance.js",
20
+ "test:rust": "node spec/tools/conformance.js --bin target/debug/stegdoc",
21
+ "test:interop": "node spec/tools/interop.js",
22
+ "test:safety": "node spec/tools/safety.js",
23
+ "test:contract": "pnpm build:native && node spec/tools/contract.js",
24
+ "test:wasm": "node spec/tools/wasm-roundtrip.js",
25
+ "test:browser": "node spec/tools/browser.js"
13
26
  },
14
27
  "keywords": [
15
28
  "cli",
@@ -54,5 +67,15 @@
54
67
  "file-type": "^16.5.4",
55
68
  "inquirer": "^8.2.6",
56
69
  "ora": "^5.4.1"
70
+ },
71
+ "devDependencies": {
72
+ "playwright-core": "^1.63.0"
73
+ },
74
+ "optionalDependencies": {
75
+ "@stegdoc/binding-win32-x64-msvc": "6.0.0",
76
+ "@stegdoc/binding-linux-x64-gnu": "6.0.0",
77
+ "@stegdoc/binding-linux-arm64-gnu": "6.0.0",
78
+ "@stegdoc/binding-darwin-x64": "6.0.0",
79
+ "@stegdoc/binding-darwin-arm64": "6.0.0"
57
80
  }
58
81
  }