Security Policy
Mutant is a language for security research and digital forensics. Its binaries sign and encrypt bytecode, detect tampering, and read memory, processes and disk images.
Mutant is a language for security research and digital forensics. Its binaries sign and encrypt bytecode, detect tampering, and read memory, processes and disk images. That combination means "is this a vulnerability?" has a sharper answer here than in most projects, so this document says where the line is before asking you to judge it.
Reporting a vulnerability#
Use GitHub private vulnerability reporting. Go to the Security tab of this repository and open a draft advisory. It is private to the maintainers until published.
Do not open a public issue, pull request, or discussion for a security report. Do not post a proof of concept anywhere public before the advisory is published.
A useful report contains:
- the affected version (
mutant --version) and platform; - what an attacker gains, in one sentence;
- a minimal reproducer — a
.mutsource, a.muartifact, or a crafted image file. Attach files to the advisory rather than pasting hex; - whether it reproduces under
--compatas well as the default--secure.
What to expect:
| Stage | Target |
|---|---|
| Acknowledgement | 3 business days |
| Initial assessment (in scope? severity?) | 10 business days |
| Fix or a dated plan | 30 days for high and critical |
| Coordinated public disclosure | 90 days, or on release, sooner if agreed |
Reporters are credited in the advisory and in CHANGELOG.md unless you ask not to be.
Advisory identifiers#
This project is not a CVE Numbering Authority and cannot assign a CVE. A confirmed vulnerability is published under an identifier in Mutant's own namespace -- MVF-2026-0001, for Mutant Vulnerabilities & Fixtures, the year it was assigned and a four-digit serial -- and listed in docs/advisories/.
An MVF never replaces an identifier somebody else assigned. A CVE or a GitHub advisory for the same issue is recorded beside it, and a vulnerability in a dependency keeps that project's identifier.
What gets one. All four of:
- It breaks a promise listed under "In scope" below.
- It has been reproduced. An unreproduced report is triaged, not numbered.
- It is reachable in a published release, or on a path a release would reach. Code fixed before it ever shipped is a changelog entry.
- It has a fixture: the test, in this repository, that fails on the
unfixed code and passes on the fixed code. That is the second half of the
name. An advisory whose fix has nothing holding it in place is not
finished, and
go test ./cmd/gendocs/fails when a published advisory names a test the tree does not have.
Hardening with no demonstrated impact does not get one, and neither does a fix whose only effect is to make a refusal arrive in the documented order. Numbering those would turn the namespace into a synonym for "changed something under security".
When it is assigned. When the advisory is written, which is when the fix is ready to publish -- not when the report arrives. So the serials are in neither severity order nor discovery order, they are allocated one at a time, and they are never reused or renumbered. Gaps carry no information, and the number of issues still unfixed cannot be read off the register.
Severity is this project's own S1-S5 scale, used to order triage. S1 is reserved for a silent break of an evidence or confidentiality promise: one where the operator is given no reason to doubt a result that is wrong.
A CVSS:3.1 and a CVSS:4.0 base vector are published alongside every
identifier, in docs/advisories/ and again in the
header table of each page that has one. They are this project's own
assessment and not a numbering authority's: the S1-S5 scale above is what
triage actually uses, and the vectors are there so a reader who works in CVSS
can compare these issues with others, and argue with the scoring rather than
guess at it. Until 2026-10-06 this section said no vector was published,
which had stopped being true: every identifier then assigned carried two, and
go test ./cmd/gendocs/ had been refusing a published advisory without both.
Supported versions#
Security fixes land on the latest released minor version. There are no long-term-support branches.
| Version | Supported |
|---|---|
| 2.6.x | ✅ current |
| 2.5.x and earlier | ❌ upgrade |
In scope#
Anything that breaks a promise the toolchain makes to the person running it:
- Artifact integrity — producing a
.muor standalone binary that passes signature verification without the trusted signer's key; re-signing a modified artifact so tamper detection stays silent; downgrade or replay of an older artifact past a check meant to reject it. - Fail-open in secure mode.
--secureis fail-closed by design. Any input that turns a verification failure into a successful run is a vulnerability, including malformed artifacts that make a check error out and get skipped. - Confidentiality of the payload. Recovering plaintext bytecode or constants from an artifact at rest without the password, or from a core dump / swap / crash report of a running process, beyond what §"Not in scope" allows.
- Credential handling. Any path that writes a password to disk, argv, an environment block, or a log. See docs/CONFIGURATION_POLICY.md §4.
- The Lua sandbox. A
.mutprogram's embedded Lua reaching the filesystem, network, or host process outside the executor's documented surface. - Memory safety of the VM and the parsers. A crafted
.mu, disk image, registry hive, EVTX log, SQLite database or PE file that drives the VM or a forensic parser into an out-of-bounds read, an unbounded allocation, or a panic that escapes as anything other than a Mutant error. Forensic input is hostile by definition — a parser that trusts a length field is a bug, not a limitation. - Path handling. Traversal or symlink following that lets a parsed artifact write outside the directory the operator named.
- The language server.
mlspparses untrusted source from an editor; anything that turns opening a file into code execution is in scope. - Compiling, as against running. Compiling a program is expected to
inspect it, not to run it -- and
mutant gen,mutant test, a debug launch and a plainmutant program.mutall compile before anything runs. Anything that makes compilation perform input or output is in scope, even where running the same program would have been allowed to do it, and whether it is the named file or a module it imports that does it. See MVF-2026-0001. - Supply chain. A compromised or unexpected dependency, a released binary whose hash does not match its provenance, or a build script that fetches code at build time.
Not in scope#
These are known and deliberate. Reporting them is not useful; they will be closed as documented behaviour.
The obfuscation ceiling. Encrypted bytecode, polymorphic emission, anti-debug and anti-tamper raise the cost of analysis. They do not make it impossible, and they are not claimed to. docs/SECURITY_LLD.md §2.2 states the non-objectives plainly: no malware-grade anti-debug resistance (not achievable in pure userland), no hardware root of trust, no remote attestation. Someone holding the binary, the password and a debugger will reach the bytecode. That is the threat model, not a hole in it.
Adversaries above the process. Kernel-level arbitrary memory write, hypervisor introspection, and physical attacks on hardware secrets are out of scope for the current release (docs/SECURITY_LLD.md §3.2).
A program doing what its author wrote. Mutant's standard library reads
process memory, enumerates handles, scans remote hosts and parses raw disks
because that is the job. A .mut script using those builtins is the tool
working. Running an untrusted .mut file is equivalent to running an untrusted
executable, and the toolchain does not currently claim otherwise. Capability
configuration is under design; no interface is specified. This is about
running one: compiling an untrusted program is a separate promise, it is
in scope above, and the difference is the whole of MVF-2026-0001.
Weaker modes behaving weakly. --compat and --dev deliberately downgrade
verification and tamper response so that debugging and analysis are possible.
A check that --secure enforces and --compat does not is the documented
difference between them.
Antivirus detections. Mutant binaries carry anti-debug, anti-tamper,
process scanning and polymorphic bytecode — the behavioural signature of packed
malware. False positives are expected and are not vulnerabilities. If a scanner
flags a release binary, please open a normal issue with the vendor and detection
name so it can be submitted for allowlisting; --compat disables the protections
that trip heuristics in an analysis lab.
Findings from a scanner, unreproduced. A CVE in a transitive dependency that no Mutant code path reaches, or a static-analysis hit with no demonstrated impact, needs a reproducer before it is a report.
Safe harbour#
Research conducted in good faith under this policy — testing against your own systems and your own artifacts, not accessing other people's data, not degrading anyone's service, and giving us the disclosure window above — is welcome, and we will not pursue or support action against you for it. This is a statement of intent from the maintainers, not legal advice, and it cannot waive third parties' rights.
Further reading#
- docs/SECURITY_LLD.md — objectives, threat model, and the implementation-accurate design.
- docs/BINARY_ARTIFACT_SECURITY_DEEP_DIVE.md — signing, signer trust, and the runtime decision matrix.
- docs/SECURITY_RUNBOOK.md — operational posture.
- docs/SANDBOX_DETECTION.md — what the detection probes read and what they may do about it.
- docs/CONFIGURATION_POLICY.md — why nothing is configured through the environment.