strhide derives encryption keys from user passwords, rewrites carrier files in place, and ships a steganography engine. Security reports are treated as first-class issues.
| Version | Supported |
|---|---|
0.9.x (latest patch only) |
Yes |
earlier than 0.9 |
No |
Before 1.0, security fixes land on the latest release line only, and there are no backports. Wire formats may still change between releases, so carriers written by an older version may become unreadable after an upgrade — see CHANGELOG.md and the stability classes in docs/api/spec/README.md.
Do not open a public issue for a security problem.
- Preferred — GitHub Security Advisory: Security tab → Report a vulnerability. This keeps the discussion private and gives us a CVE path.
- Alternative — email
mcxiaoke@live.comwith[strhide-security]in the subject.
Please include:
- affected version (
strhide --version) and platform; - the command or API path involved (
write/read/restore/embed/extract/capacity/inspect/verify/scan, or thefacade::StrHidemethod); - a minimal reproducer when the input can be shared, or a precise description when it cannot;
- what an attacker gains (key recovery, payload disclosure, silent data corruption, signature bypass, code execution, …).
Never attach real secrets or real confidential payloads.
| Stage | Target |
|---|---|
| Acknowledgement | 7 days |
| Triage and severity assessment | 14 days |
| Fix or written mitigation plan | after triage, timeline agreed in the advisory |
| Coordinated disclosure | 90 days from the report (sooner if the issue is exploited in the wild) |
These are best-effort targets, not contractual SLAs. Reporters are credited in CHANGELOG.md unless they ask to stay anonymous.
- Key derivation and AEAD — PBKDF2/HKDF parameter handling, domain separation,
AADconstruction, nonce derivation, cipher-suite confusion between the two domains. - Credential handling — key material lifetime and wiping, credential sources (
-p,--password-file,-e,STRHIDE_PASSWORD,StealthProfile/StegoProfiledefaults), unintended leakage into logs, backups, temporary files, or the GUI log console. - File handling — atomic commit and stale temp-file reclamation, in-place backup behaviour, path handling on output (
-o), permission/timestamp preservation. - Carrier patching — PE
codebits/slack/overlay patching, PDF incremental update, ZIP/JAR central-directory injection, refusing signed carriers. - Parsers — memory safety, integer overflow, unbounded allocation, and time complexity when reading hostile carriers (truncated, huge, malformed, adversarial chunk/box tables).
- CLI and GUI surface — argument parsing, the Tauri IPC command set, the desktop client's CSP and capability configuration, drag-and-drop path handling.
- Supply chain — build scripts, feature combinations that unexpectedly weaken defaults, published crates whose behaviour differs from this repository.
These are documented, intentional behaviours. Reporting them as vulnerabilities is not actionable; if you believe one of them is worse than documented, say so explicitly and we will re-review.
| Boundary | Why it is by design |
|---|---|
Password-less envelopes are readable by any build. Both domains fall back to public constants (PUBLIC_DEFAULT_SECRET for content, BUILTIN_DEFAULT_KEY for structure) when no password, key file, or profile secret is supplied. |
Interoperability between stock binaries. Supply a password or a private dialect by default. |
--stealth does not improve steganalysis resistance. It derives the header/trailer magic and masks header fields from the password, so a no-password probe sees noise; the statistical detectability of the payload is unchanged. |
It is an attribution/plausible-deniability feature, not a detection-resistance feature. See docs/PRIVATE_BUILD.md §6. |
Structure-domain markers are visible in non-stealth mode. prIv (PNG), stEg/SHRD, MP4_UUID_NEUTRAL, and the JPEG APP15 marker can be found by static scanners. |
Cost-free bit-exact restoration is built on format-legal structure, which must sit somewhere in the container. |
| Content-domain embeds are irreversible. The bytes of the media itself change, so no pre-write host can be recovered. | Fundamental to content-adaptive steganography. |
| Extraction failure is indistinguishable from a wrong password. The content domain returns the same "no hidden data" result for a wrong key, a wrong dialect, and an absent payload. | Not leaking whether a carrier holds a payload is the point. Do not expect a more specific error. |
restore fails closed with RestoreMismatch when the carrier changed after the write (for example a later content-domain embed, or any re-encode) instead of emitting a non-matching file. |
Emitting a wrong file silently would be worse than refusing. |
Signed carriers are refused (Rejection): PE Authenticode, PDF ByteRange signatures, and signed JARs are rejected because any injection invalidates the signature. |
Silently breaking a signature is a security regression, not a feature. |
A lower PBKDF2 iteration count is refused on read. Readers reject envelopes whose declared iteration count is below max_pbkdf2_iterations (default 200 000 for the content domain). |
Prevents a downgrade attack that makes brute force cheap. |
Documented for reviewers; the normative source is docs/api/spec/CRYPTO.md.
- Primitives — PBKDF2-HMAC-SHA256 (structure domain: 100 000 iterations; content domain: 200 000 for passwords, skipped for 32-byte file keys), HKDF-SHA256, AES-256-GCM (both domains, default suite) and ChaCha20-Poly1305 (content domain, suite 2; readers cross-reject the wrong suite).
- Nonces — structure domain derives per-chunk nonces from a base nonce by XOR-ing the chunk index into the last 4 bytes, with
0xFFFFFFFFreserved for the metadata block; chunk index andis_lastare bound in the AAD. - AAD binding — the structure domain authenticates the 80-byte header prefix (host size + host SHA-256 + strategy + salt + nonce) plus hint/meta lengths; the content domain authenticates a 28-byte envelope prefix,
format_id, the full base header, and the extension block. - Key lifetime — derived keys are not
Copyand are overwritten on drop withwrite_volatileplus a compiler fence. - Atomic writes — output is written to
<target>.strhide.<pid>.<r>.tmpin the destination directory and renamed over the target; stale markers from killed processes are reclaimed by rename before reuse. - No network, no telemetry — the crate has no HTTP client dependency and the CLI/GUI make no outbound connections.
- Configuration lookup excludes the current directory, so a
strhide.tomldropped next to an untrusted working directory is never picked up implicitly.
The implementation has not been through an independent third-party cryptographic audit, and this repository contains no audit report. Treat the cryptographic design as "reviewed by the author, specified in public, not externally audited". The normative specification is public precisely so that others can review or reimplement it.
- Prefer
--password-fileorSTRHIDE_PASSWORDover-p: a password passed as an argument is visible in the process list and may land in shell history. - Remember that
--in-placewrites a.baknext to the carrier by default; that backup is an unencrypted copy of the original, and deleting it is on you. - Keep the original stego carrier. Any lossy re-encode, crop, resize, or recompression destroys content-domain payloads permanently.
- If you need isolation from stock builds rather than confidentiality with the maintainers, build a private dialect as described in docs/PRIVATE_BUILD.md.
- Legal and dual-use considerations are in LEGAL.md; the risk and liability statement is in the DISCLAIMER (中文).