Security Low-Level Design (LLD)
This document defines the low-level security design for Mutant's bytecode generation, signing, transport, loading, verification, runtime execution, anti-debugging, integrity monitoring, and telemetry…
1. Document Purpose#
This document defines the low-level security design for Mutant's bytecode generation, signing, transport, loading, verification, runtime execution, anti-debugging, integrity monitoring, and telemetry paths.
It is implementation-accurate to the current codebase and intended for:
- Security engineering and code reviews
- Incident response and operations enablement
- Regression prevention for future security changes
- Test strategy and policy enforcement
This LLD focuses on anti-tamper and anti-piracy controls under an offline-first threat model.
Companion deep dives:
2. Security Objectives#
2.1 Primary Objectives#
- Ensure tampered artifacts are detected before or during execution.
- Ensure secure mode is fail-closed by default.
- Protect payload confidentiality and integrity at rest.
- Reduce effectiveness of dynamic analysis/debugging.
- Detect runtime instruction tampering and respond per policy.
- Provide observable security signals via telemetry and audit output.
2.2 Non-Objectives#
- Absolute malware-grade anti-debug resistance (not possible in pure userland).
- Hardware-rooted trust chain (not implemented yet).
- Remote attestation and centralized key management (not implemented yet).
3. Threat Model#
3.1 In Scope Adversary Capabilities#
- Reads and modifies
.muartifacts on disk. - Attempts re-signing with attacker-generated keypairs.
- Runs runtime under debugger/instrumentation.
- Patches bytecode/in-memory instruction regions.
- Replays older artifacts or malformed payloads.
- Manipulates environment variables at launch.
3.2 Out of Scope (Current Release)#
- Kernel-level adversaries with arbitrary memory write.
- Hypervisor-level introspection attacks.
- Physical attacks against hardware secrets.
4. Security Modes and Policy Semantics#
Mutant currently has three practical launch postures:
The definitive operator-facing table lives in EXECUTION_MODES.md; this section states the design behind it.
- Secure mode (
--secure, default):
- Runtime posture defaults to secure execution gates.
- Embedded self-verification always runs. Trusted signer pinning is an upgrade
on top of it, enabled by
--signer-auth. - Default tamper response is
terminate, printing the detector that fired, the reason, and the remedy. - If
--trusted-key <path>is not passed, the runtime bootstraps a local persistent keypair and uses the local public key as the trusted key for signer-auth verification.
- Compatibility mode (
--compat):
- Signature verification uses the embedded signer key (format validity only) -- the same floor secure mode has, not a substitute for it.
- Default tamper response is
warn.
- Developer mode (
--dev):
- Runtime behavior is forced to compatibility mode.
- If no runtime password is supplied for
.mu, default deterministic local password fallback is used (mutil.GetPwd()). - Intended for local testing convenience, not production hardening.
4.1 Mode Resolution Rules#
A command line names at most one mode. --secure --compat, --secure --dev and
--signer-auth --no-signer-auth are rejected with a non-zero exit naming both
flags (validateModeFlags, main.go), before any other work.
--compator--dev-> mode becomes compatibility.--dev --compatis accepted: dev mode implies compatibility mode, so naming both is redundant rather than contradictory. Repeating a flag is harmless.
This used to be "last matching mode flag wins" due to linear arg scanning, with
--dev winning over --secure regardless of order. mutant prog.mu --dev --secure therefore ran unsecured, having been asked in the same breath to run
secured, and said nothing about it.
4.2 Tamper Response Policy#
Policy source: the execution mode, from the command line. Nothing else.
ResolveTamperResponse(secureMode) in security/response_policy.go is a pure
function of it.
Possible values:
warn: log and continuedelay: sleep then continueterminate: return error/fail
Resolution:
- dev mode (
--dev):warn - compatibility mode (
--compat):warn - secure mode (the default):
terminate
delay is implemented in ApplyTamperResponse and sleeps
DefaultTamperDelayMs (a 250 ms constant), but no mode selects it. It
survives as the seam a future response policy would use.
4.3 Protection Profiles#
Fixed at standard. ResolveProtectionProfile() in security/profile.go
returns the constant defaultProtectionProfile; there is no selector, and no
precedence chain to reason about. Mutant takes no configuration from environment
variables, and posture is not a per-run decision. See
CONFIGURATION_POLICY.md.
The three profile values remain as an on-disk encoding:
minimal(code1)standard(code2) -- whatResolveProtectionProfileCode()always returns, and therefore what every V3 release trailer this build writes contains.paranoid(code3)
ProtectionProfileFromCode maps a trailer byte back to a name, which is why all
three must stay: a trailer written by an older build can carry code 1 or 3
and must still be readable.
defaultTamperResponseForProfile still switches on the profile, so its
minimal and paranoid arms exist in the source. They are unreachable at
runtime, because the value it switches on is a constant.
5. Component Architecture#
5.1 Security-Critical Modules#
security/crypto.go
- AES-GCM payload encryption/decryption
- metadata serialization/parsing
security/kdf.go
- Argon2id derivation and parameter validation
- deterministic HKDF utilities
security/signing.go+security/signatures.go
- Ed25519 signatures
- signed artifact envelope parse/verify
- trusted key pinning
security/secure_random.go
- ChaCha20 offset-aware stream masking
- secure compare/zeroing helpers
security/response_policy.go
- central tamper response decisions
security/telemetry.go
- counters, JSON snapshot/export, audit log
security/antidebug_*.go
- platform-specific debugger detection
runner/runner.go
- execution gate and staged enforcement
vm/vm.go
- runtime decode path + integrity probes + policy integration
code/code.go
- offset-aware operand reads
object/secure_memory.go
- secure wrappers and object offset conventions
6. Artifact Format and Cryptographic Construction#
6.1 Signed Artifact Envelope (`.mu`)#
Outer format:
HEADER |-| ENCODED_DATA |-| SIGNATURE_HEX |-| PUBLIC_KEY_HEX |-| FOOTER
Constants:
HEADER = MUTFOOTER = ANTOUTER_SEPERATOR = |-|
Notes:
- Parser validates minimum parts and header/footer sentinels.
- Signature/public key are hex encoded.
ENCODED_DATAis the serialized encryption metadata string.
6.1.1 Standalone Release Trailer#
Release binaries produced by the generator append a standalone trailer after the embedded runtime payload.
Current format version: V3
MUTANTBC | version | payload_len | payload_sha256 | canary | profile_code | provenance_sha256
Field details:
version
- V1 and V2 are accepted for legacy compatibility.
- V3 is the current emission format.
payload_len
- Big-endian
uint64payload length.
payload_sha256
- SHA-256 over the embedded bytecode payload.
canary
- Short SHA-256 derived guard used to detect trailer corruption.
profile_code
- Encoded protection profile selected at build time.
1 = minimal,2 = standard,3 = paranoid.
provenance_sha256
- SHA-256 over
payload || payload_sha256 || profile_code. - Used to detect tampering and mismatched release provenance.
Compatibility rules:
- Runner validates V3 first, then V2, then V1.
- V1 and V2 remain supported for older artifacts.
- V3 is required for new release builds.
6.2 Encrypted Metadata Payload#
ENCODED_DATA format in password mode:
ciphertext_b64 | salt_hex | <empty-sourcehash> | true | iter | memory | threads
Deterministic mode variant stores sourceHash and false.
Constants:
- inner separator:
SEPERATOR = | - AEAD associated data:
ENCSIG = MUTANT
6.3 Encryption Layers#
Compilation encryption layering (current implementation):
- VM bytecode and constants are stream-masked (
mutil.EncryptByteCode,SecureXOR). - Full gob byte slice is XOR-wrapped with embedded random key
(
SecureXOREncrypt). - Result is AES-GCM encrypted using Argon2id-derived key (
AESEncrypt). - Encrypted metadata is signed with Ed25519 (
SignCode).
Rationale:
- AES-GCM gives confidentiality + integrity for payload.
- Signature protects authenticity and allows trusted signer pinning in secure mode.
- Additional stream/XOR layers increase analysis friction.
The standalone release trailer adds a separate provenance check path so the runtime can validate the generated build profile and trailer integrity before payload decode.
7. Key Derivation and Password Handling#
7.1 Password KDF (Argon2id)#
Derivation path:
DeriveKeyFromPassword(password, salt)- defaults:
- time: 1
- memory: 64 MB (
64*1024KB) - threads: 4
- key length: 32 bytes
Validation hard bounds:
- time:
[1..8] - memory:
[64MB..4GB]in KB units - threads:
[1..16]
7.2 Password Quality Policy#
Easy memorable passwords
7.3 Build-Time Profile Binding#
Release generation binds the selected protection profile into the standalone
trailer via the profile_code field.
Security implication:
- The build mode becomes auditable in the emitted artifact.
- Trailer provenance mismatch is treated as tamper.
- Policy controls are fixed or flag-driven; nothing in the environment can override them.
7.4 Deterministic Local Password Fallback#
mutil.GetPwd() uses HKDF-SHA512 over fixed context constants to produce a
deterministic local key string.
Used when:
- Single-arg
.mut/.muconvenience paths. --devmode running.muwithout a password source.
Security implication:
- deterministic and recoverable from binary/source; convenience only.
- not a production secret.
8. Signing and Trust Model#
8.1 Signature Primitive#
- Algorithm: Ed25519
- Signing input: SHA-256 hash of
ENCODED_DATA
8.2 Verification Paths#
- Compatibility verification (
VerifyCode):
- Parses embedded public key and signature.
- Verifies signature correctness for envelope integrity.
- Does not enforce signer identity trust.
- Secure verification (
VerifyCodeWithTrustedPublicKey):
- Parses embedded public key/signature.
- Loads the trusted key from the file named by
--trusted-key <path>when given. A missing, malformed or wrong-sized file is a hard error, never a silent fallback. - Falls back to local key bootstrap resolution when the flag is absent.
- Constant-time compares embedded key with trusted key.
- On mismatch returns
ErrUntrustedSigner. - Verifies signature using trusted key.
8.3 Signing Key Injection#
Build/signing key source:
- The local persistent keypair under
<home>/.mutant/keys, loaded or bootstrapped byEnsureLocalSigningKeyPair(generator/generate.go->loadOrBootstrapSigningPrivateKey). Creating it prints both paths to stderr.<home>is the home directory the account database records, neverHOMEorUSERPROFILE(ResolveLocalKeyStoreDir). - A caller of
generator.Generatemay pass aprivateKeydirectly. Nothing on the CLI path does.
Signing key material never comes from the environment: it is the most sensitive value in the system, and an environment variable is inherited by every child process and invisible in the invocation. See CONFIGURATION_POLICY.md.
Operational risk:
- unmanaged local bootstrap keys can diverge from official release trust anchors
if not governed by deployment policy. Verifying with
--trusted-keypinned to the approved signer is what closes this.
9. Runtime Security Gate (`runner.Run`)#
9.1 Telemetry Export Hook#
None. ExportSecurityTelemetry(path) exists and writes the counter snapshot as
JSON, but the runner does not call it and no flag selects a path. Counters are
readable in-process through SecurityTelemetrySnapshot() and
SecurityTelemetryJSON().
9.2 Standalone Trailer Validation#
Before signature verification and decode, runner performs standalone trailer validation when the binary appears to carry an appended payload.
Validation order:
- V3 trailer
- V2 trailer
- V1 trailer
Checks performed:
- trailer marker and version
- payload length bounds
- payload SHA-256
- canary (V2+)
- profile code validity and provenance hash (V3)
Failure handling:
- checksum mismatch -> reject payload
- canary mismatch -> reject payload
- provenance mismatch -> reject payload
- unsupported trailer version -> reject payload
9.3 Process-Protection Probe Enforcement#
At both pre-decode and pre-execution stages, runner evaluates process
protection probes (process_injection, trampoline, iat_got,
module_integrity, memory_page_anomaly). antiTamperProbeEnabled is a
compile-time true, so they always run.
Rollout gate:
isProcessProtectionEnabled()(runner/runner.go) controls whether the runner executes process-protection enforcement. It returnstrue.- Not configurable. The runner reaches it through the
processProtectionOnpackage variable, which tests replace to exercise the skip path.
Enforcement threshold:
- Any signal with
detected=trueandconfidence >= 80is treated as a process protection event.
Response path:
- Telemetry event:
process_protection_detected - Policy dispatch:
ApplyTamperResponse(process_protection_detected, ...) - Default result: terminate in secure mode, warn in compatibility/dev mode.
9.4 Remote Process Scan Enforcement#
Runner executes RunRemoteProcessScan at both pre-decode and pre-execution
stages.
Gates and modes -- all from remoteScanConfigState
(security/processscan_config.go), which ships disabled and is writable only by
SetRemoteScanConfigForTesting:
Enabledgates scan execution. It isfalse, so a shipped binary never scans.Mode(off|observe|enforce, defaultobserve) controls decision behaviour.- Scanner errors are telemetry-visible and non-blocking.
Current enforcement behavior:
observe: records telemetry only, never blocks execution.enforce: blocks only when a verdict reaches the configured critical score.- High-risk but non-critical verdicts remain advisory.
Current implementation status:
- Configuration, correlator, telemetry, and runner policy integration are implemented.
ScanRemoteProcessesWindowscurrently returns no verdicts (safe no-op), so remote verdict generation is scaffolding-ready but not yet signal-rich.- There is no way to switch it on outside a test. When one is added it will be a flag, not an environment variable. See CONFIGURATION_POLICY.md.
10. VM Runtime Protection Internals#
10.1 Offset-Aware Instruction Decode#
VM fetch path decrypts opcode/operands at exact stream offsets:
- opcode:
SecureXOROneAt(ins[ip], seed=inslen, password, offset=ip) uint16operands:ReadUint16(..., offset=ip+1)uint8operands:ReadUint8(..., offset=ip+1)
Security effect:
- avoids fixed-position-independent keystream reuse.
- ties decrypt stream byte to logical position.
10.2 Runtime Integrity Baselines#
- Each compiled function instruction slice gets SHA-256 baseline at registration.
- Main function baseline seeded at VM init.
- Additional function baselines registered when frames are pushed.
10.3 Probe Scheduler#
runIntegrityProbes() triggers checks by schedule:
- periodic: every
integrityEverysteps (default 64) - jittered: when
stepCount % 97 == integrityJitter % 97 - sweep: every 251 steps checks all active frames
10.4 Integrity Response#
If current instruction hash differs from expected baseline:
RecordIntegrityFailure(stage)ApplyTamperResponse(integrity_failed, stage, vm.secureMode, err)
Important implementation detail:
- The VM passes its own
secureMode, which carries the launch mode, so an integrity failure follows the same rule as every other tamper event:terminateunder the default posture,warnunder--compatand--dev. - This section previously claimed the VM forced
secureMode=truehere, making integrity the one check no mode could downgrade. It does not, and has not sinceNewWithPasswordModebegan threading the launch mode into the VM. Whether it should be the exception is an open question -- the difference between "the host looks suspicious", which is a guess about the environment, and "this bytecode is not what was signed", which is not. It is recorded here rather than decided.
A termination prints the detector, the reason and the remedy before the run
stops -- see ExplainTamperTermination in
security/response_policy.go and
EXECUTION_MODES.md. The VM's security opcodes return their
error directly rather than going through the response policy, so they call the
explainer themselves.
The probe schedule of 10.3 feeding this response, end to end. The delay
branch is the seam described in 4.2: ApplyTamperResponse implements it, and
no mode selects it, so a detection reaches WarnContinue or Terminate.
10.5 Builtin Capability Configuration#
There is none, and none is planned. This is a decision taken on 2026-10-10,
not an unfinished design: a Mutant program carries the authority of the process
that runs it, the same way a program written in Python, Ruby, Node or Go does.
A program that can reach exec_string, exec_argv or cmd_run can run
commands, and one that can reach the fs_*, net_* or http_* families can
read files and open sockets, with this process's own permissions.
The policy_* builtins are not this control under another name: they wrap
Open Policy Agent, so a program loads a Rego module and asks it a question,
and the program itself decides what to do with the answer. Nothing outside
those five builtins calls the engine, so it is never consulted before a
builtin runs.
What constrains command execution is fixed rather than configurable -- a
3-second timeout bounding the whole call, 8 KiB kept of each output stream, and
a kill that reaches the process tree -- and those bound blast radius, not
permission. The operator's decision is whether to run the program at all, which
is what the trust model of section 8 and the mode semantics of section 4
support, not a capability set. Note 1 under the control matrix in
SECURITY_LLD_TRACEABILITY.md records the
decision and the reasoning, and SEC-017 there carries status D.
11. In-Memory Object Protection#
11.1 VM Stack/Object Paths#
push()attempts to encrypt objects before storing.pop()attempts to decrypt encrypted objects before use.- arrays/hashes decrypt elements during construction.
- builtin call arguments decrypted before invocation.
11.2 Secure Wrappers (`object/secure_memory.go`)#
Available primitives:
SecureGlobal
- encrypted value storage, typed set/get, secure clear.
SecureStack
- optional auto-encrypt idle entries after timeout.
SecureConstantPool
- encrypted constants with cache.
Current wiring note:
- VM runtime protection path primarily uses
mutil.EncryptObjectandmutil.DecryptObjectin stack/global/constant handling. object/secure_memory.goprovides additional wrappers and utilities that can be used by integrations, but they are not the primary VM storage path today.
Current policy gate:
- None.
resolveVMGlobalMemoryMode()(vm/vm.go) returnsruntimeunconditionally, the performance-safe path. wrapperexists as a named constant and an unreached branch. It is the seam a future hardening mode would use; there is no way to select it.
Current helper-scope decision:
- no additional secure-memory helper types are required for this release beyond
SecureGlobal,SecureStack, andSecureConstantPool - revisit helper expansion only if threat-model requirements exceed current runtime encryption + optional wrapper mode coverage
11.3 Stream Offset Convention for Object Types#
objectStreamOffset:
- integer -> 64
- string -> 128
- boolean -> 192
- default -> 256
Purpose:
- deterministic per-type stream segmentation and reduced overlap.
Limitations:
- plaintext exists transiently during runtime operations and conversions.
- Go string immutability limits guaranteed in-place wipe.
12. Anti-Debug Design#
12.1 Entry Point#
IsDebuggerPresent() dispatches by OS:
- windows ->
isDebuggerPresentWindows - linux ->
isDebuggerPresentLinux - darwin ->
isDebuggerPresentDarwin
12.2 Windows Signals#
High-confidence (single hit triggers):
IsDebuggerPresentCheckRemoteDebuggerPresentNtQueryInformationProcess(ProcessDebugPort)NtQueryInformationProcess(ProcessDebugObjectHandle)
Low-confidence weak hits (threshold-based):
OutputDebugStringtiming anomaly- common debugger DLL presence
Decision rule:
shouldTriggerDebuggerByWeight(highConfidence, weakHits, weakThreshold=2)
12.3 Linux Signals#
/proc/self/statusTracerPid != 0- parent commandline pattern matching for debugger/re tools
- debugger-related env markers (
GDB_*,LLDB_*,VALGRIND_*,LD_PRELOAD, etc.)
12.4 Darwin Signals#
sysctlP_TRACED flag- parent process pattern match
- debugging env markers (
LLDB_*,DYLD_INSERT_LIBRARIES, etc.)
12.5 Enforcement Staging#
Runner checks anti-debug at:
pre-decodepre-execution
On detection:
- telemetry increment
- policy application (
warn/delay/terminate)
13. Telemetry and Audit#
13.1 Counters#
Atomic counters:
debugger_detectedintegrity_failedsignature_failedsandbox_detectedprocess_protection_detectedanti_tamper_probe_invokedanti_tamper_probe_errorremote_process_scan_invokedremote_process_scan_errorremote_process_suspiciousremote_process_criticalcommand_attemptcommand_blockedcommand_succeededcommand_failed
13.2 APIs#
SecurityTelemetrySnapshot()-> mapSecurityTelemetryJSON()-> serialized JSONExportSecurityTelemetry(path)-> file write mode0600ResetSecurityTelemetry()-> test/ops reset
13.3 Audit Stream#
Not implemented. auditEvent(event, stage) is called by every Record* counter
function but its body discards both arguments. The stderr line format it once
emitted was [security-audit] ts=<unix> event=<...> stage=<...>.
14. Error and Failure Semantics#
14.1 Canonical Security Errors#
ErrWrongSignatureErrPasswordRequiredErrInvalidMetadataErrDebuggerDetectedErrSandboxDetectedErrProcessProtectionDetectedErrUntrustedSigner
14.2 Policy-Dependent Behavior#
Same event can either:
- terminate with original error (
terminate) - continue with warning (
warn) - continue after delay (
delay)
14.3 Design Tradeoff#
- Secure defaults maximize resistance but may impact debuggability.
- Compatibility/dev improves operator ergonomics but weakens security guarantees.
15. Detailed Dataflow#
16. Testing and Verification Strategy#
16.1 Unit/Integration Coverage Highlights#
security/security_test.go
- trusted key pinning success/failure
- argon2 bounds validation
- stream XOR offset correctness
- telemetry counters/json/export
- policy defaults and response behavior
- compatibility mixed artifact behavior
- anti-debug weighting logic
- anti-tamper probe routing and process-protection signal set
runner/runner_test.go
- secure mode malformed/tampered payload rejection
- compatibility mode continue-on-signature-failure behavior
- secure-mode termination on high-confidence process-protection signal
- advisory behavior for sub-threshold process-protection signal
vm/vm_security_policy_test.go
- integrity tamper behavior under
warn,delay,terminate
16.2 Running the Security Profile#
The project runs no CI. This section described a
.github/workflows/security-profile.yml that was never written, and the
repository-wide workflow that did exist has been removed; the security suites
are run the same way every other suite is, by hand before a release:
CGO_ENABLED=0 go test ./security/... ./runner/... ./vm/... ./policy/...
go test ./... covers all of it, and the strict defaults under test
(terminate, delay=0, audit=1) are the defaults in the code, not settings a
runner supplies — there is nowhere else for them to come from, because Mutant
takes no configuration from the environment.
17. Security Invariants (Must Hold)#
- When
--signer-authis enabled in secure mode, trusted signer verification must be enforced (the key given by--trusted-key, or the local bootstrap trusted key). - In signer-auth path, signature mismatch/untrusted signer must fail unless
policy is explicitly downgraded by launch mode (
--compat/--dev). - Integrity mismatch must always record telemetry before policy action.
- Metadata parser must reject malformed Argon2 parameters.
- Opcode/operand decode must stay offset-aware.
- Telemetry export path must not fail open to panic.
18. Residual Risks and Limitations#
- Compatibility/dev mode can be misused in production if not controlled.
- Env-driven policy downgrades (
warn/delay) can reduce enforcement. - Userland anti-debug remains bypassable by binary patching.
- No hardware trust anchor or attestation.
- Deterministic fallback password is convenience-only and not secret.
- Full Windows runtime integration tests on clean Windows hosts remain pending, and with no CI there is no automated coverage of the other two platforms either — a release is validated on whichever host cuts it.
- Full compile-sign-run-tamper rerun lifecycle tests remain incomplete.
19. Operational Guidance#
Mutant takes no configuration from environment variables, so an operational baseline is a command line and nothing else. See CONFIGURATION_POLICY.md.
19.1 Recommended Production Baseline#
- Launch with
--secure --signer-auth. - Pass
--trusted-key <path>naming the release signer's public key file. Without it, verification falls back to a locally bootstrapped keypair, which trusts whatever signed the artifact on this host. - Do not pass
--compator--dev. Secure mode already resolves the tamper response toterminate, and those flags are the only thing that changes it. - Record the full command line with the artifact. It is the complete description of how the run was configured.
19.2 Developer Baseline#
- Use
--devfor local test convenience only. - Avoid using
--devartifacts/behavior as production acceptance signal. - Because every switch is a flag, the difference between a dev run and a production run is visible in the two command lines. There is no ambient state to drift.
20. Future Hardening Backlog (LLD-Level)#
- Add compile-sign-run-tamper-rerun end-to-end suites.
- Add real Windows runner-based anti-debug integration tests.
- Add signer key rotation and multi-key trust set support.
- Add self-integrity hashing of selected runtime text/function regions.
- Add policy lock mode to ignore mode-based downgrades in production builds.
- Consider hardware-backed key protection for signing workflows.
- Expand telemetry schema with per-event reason codes and monotonic sequence IDs.
21. Appendix A: Key Functions by Responsibility#
Build-Time#
generator.Generategenerator.encryptCodesecurity.AESEncryptsecurity.SignCode
Run-Time Gate#
runner.Runrunner.enforceAntiDebugrunner.decryptCode
Runtime VM#
vm.Runvm.runIntegrityProbesvm.verifyFrameIntegrity
Security Core#
security.VerifyCodesecurity.VerifyCodeWithTrustedPublicKeysecurity.ApplyTamperResponsesecurity.Record*telemetry APIssecurity.IsDebuggerPresent
22. Appendix B: Mode/Policy Decision Table#
| Execution Posture | Signature Verification Path | Default Policy | Wrong Signature / Untrusted Key | Debugger Hit | Integrity Mismatch |
|---|---|---|---|---|---|
Secure (--secure) |
Self-verify; trusted key pinning with --signer-auth |
terminate | stop | stop | stop |
Compatibility (--compat) |
Self-verify (embedded key) | warn | continue/log | continue/log | continue/log |
Developer (--dev) |
Self-verify (embedded key) | warn | continue/log | continue/log | continue/log |
The posture column is the whole input. There is no downgrade path that is not one of these three flags -- see §4.2 and CONFIGURATION_POLICY.md, and naming two of them is an error rather than a resolution (§4.1).
The integrity-mismatch column used to read "stop (VM forces secure policy)" for
the two weaker postures. It does not: vm.secureMode carries the launch mode
into both integrity checks (vm/vm.go), so under --compat and --dev a
mismatch warns and the run continues, like every other tamper event. Whether the
integrity check should be the one exception to the mode rule is an open
question; the table states what the code does.
23. Appendix C: Design Review Checklist#
- Any new security check records telemetry before response.
- Any new response path uses
ApplyTamperResponseconsistently. - Any new opcode/operand decode remains offset-aware.
- Any metadata schema changes preserve strict parse/validation behavior.
- Any new compatibility shortcut is explicitly blocked in secure mode.
- The security suites are run before a release, on the host cutting it.
End of document.