Skip to main content

Deception Markup Language (DML)

DML is a small, versioned, HMAC-signable schema for declaring deception assets — honeypots, canaries, and tripwires — as portable documents. Instead of scattering trap logic across your codebase, you describe traps once, in one file, and ship that file between systems with confidence that it has not been tampered with in transit or at rest.

A DML document is a versioned set of traps. Each trap pairs a trigger (what an attacker has to do to set it off) with a response (how the trap reacts), plus metadata (severity, MITRE ATT&CK tactic, tags) and alerting intent.

Why versioned + signed deception config matters

Deception only works if attackers can't distinguish traps from real assets — and operators can't afford to deploy traps they can't trust. Two properties make a deception config trustworthy:

  • Versioned. A document declares the DML spec version it targets, so consumers can reject documents they don't understand instead of misinterpreting them. Trap IDs are namespaced and must be unique, so two teams can merge configs without silent collisions.
  • Signed. Trap definitions are exactly the kind of high-value config an attacker who gains a foothold would love to quietly edit — disabling the tripwire that would catch them, or pointing a "block" response at log-only. DML signs every trap and the document as a whole with HMAC-SHA256, so any edit — to a field, a trap, or the set of traps — is detectable as long as the signing key stays secret.

Trigger types

DML 0.3.0 — WraithMesh sensors

v0.3.0 adds a sensors section for declaring WraithMesh nodes inside the same signed document as your traps, plus an optional mesh_policy for TIE corroboration rules.

dml_version: "0.3.0"
mesh_policy:
  tie_min_corroboration: 2
  forbid_raw_commands: true
  forbid_src_ip: true
sensors:
  - id: cowrie-east-1
    name: Cowrie observer
    sensor_class: cowrie
    cowrie_log_path: /var/log/cowrie/cowrie.json
    aggregator_url: http://127.0.0.1:8787

Export a WraithMesh mesh.json from a signed document:

DML_KEY=... WRAITHMESH_KEY=... dml sign examples/example_mesh.yaml -o signed_mesh.yaml
DML_KEY=... WRAITHMESH_KEY=... dml export-mesh signed_mesh.yaml cowrie-east-1 -o mesh.json

New trigger types: cowrie_session, canary_beacon, equivalence_match.
New response types: mesh_uplink, corroborate_alert.
Sensor classes: cowrie, canary, gateway, fingerprint.

Documents declaring dml_version: "0.2.0" remain valid (traps-only).

Trigger types

The trigger types — the kind of attacker activity that fires a trap:

Trigger type Fires when… Key fields
http_request a request hits a planted path/method path (required), method
dns_resolution a planted hostname is resolved hostname (required)
api_key_use a planted API key (by prefix) is used api_key_prefix
file_access a canary file is opened path
login_attempt a login is attempted against a decoy credential match_regex
data_access a canary database record is read record_id
timing_probe response-timing probing is detected timing_target_ms (required)
canary_email mail arrives at a canary address email (required)
jwt_use a planted/forged JWT is presented match_regex
cowrie_session a Cowrie session matches a known equivalence key equivalence_key (required)
canary_beacon a supply-chain canary token fires package_name (required)
equivalence_match any sensor sees a campaign equivalence key equivalence_key (required)

Response types

The eight response types — how a trap reacts once triggered:

Response type Effect Key fields
fake_data serve fabricated data to the attacker fake_data_template, http_status
redirect_sandbox divert the session into a sandbox/decoy environment redirect_url, sandbox_reason
delay_response tarpit — stall the response delay_ms (required)
mirror_engage engage the attacker (e.g. LLM-driven mirroring) llm_prompt_override, llm_model
block_ip block the source —
log_only record the event, no visible reaction —
alert_only fire an alert, no visible reaction —
honeypot_auth accept the credential into a honeypot auth flow http_status
mesh_uplink emit a signed observation to the TIE aggregator aggregator_url (required)
corroborate_alert request corroboration / high-priority TIE alert aggregator_url

Severity levels: critical, high, medium, low, info. MITRE tactics: the standard ATT&CK enterprise tactic set (e.g. credential_access, discovery, exfiltration).

Install

pip install .               # from a checkout
# runtime dependency: PyYAML>=6

Quickstart

from dml import DMLValidator, DMLSigner, load, dumps

doc = load("examples/example_traps.yaml")

# 1. Validate against the spec.
errors = DMLValidator().validate_document(doc)
assert not errors, errors

# 2. Sign (the key is supplied by you, never hardcoded in the library).
signer = DMLSigner.from_env("DML_KEY")     # or DMLSigner("my-secret")
signed = signer.sign_document(doc)

# 3. Verify — detects any tampering with traps or the document.
ok, errors = signer.verify_document(signed)
assert ok

print(dumps(signed, fmt="yaml"))

Build a document from your own records (no ORM/framework coupling):

from dml import DMLTrap, DMLTrigger, build_document

traps = [
    DMLTrap(id="secret-path", name="Hidden endpoint",
            trigger=DMLTrigger(type="http_request", path="/internal/.env")),
]
doc = build_document(traps, platform="my-app", namespace="prod")

Run the full round-trip example:

DML_KEY=test-key-123 python examples/quickstart.py

CLI

The signing key is read from an environment variable (default DML_KEY) — it is never passed as a command-line literal, so it can't leak into shell history or process listings.

python -m dml validate examples/example_traps.yaml
python -m dml sign     examples/example_traps.yaml --key-env DML_KEY -o signed.yaml
python -m dml verify   signed.yaml --key-env DML_KEY
python -m dml schema

Example output:

$ python -m dml validate examples/example_traps.yaml
OK: valid DML document (5 traps)

$ DML_KEY=test-key-123 python -m dml sign examples/example_traps.yaml --key-env DML_KEY -o signed.yaml
OK: signed document written to signed.yaml

$ DML_KEY=test-key-123 python -m dml verify signed.yaml --key-env DML_KEY
OK: document signature valid

$ DML_KEY=wrong python -m dml verify signed.yaml --key-env DML_KEY
FAILED: verification failed:
   - Document signature mismatch — document may be tampered

The signing model

DML signs at two levels with HMAC-SHA256:

  • Per-trap. Each trap carries a signature field — an HMAC over the trap's contents with the signature field itself excluded. This lets a consumer attribute tampering to a specific trap.
  • Whole-document. The document carries a document_signature — an HMAC over the document identity fields (dml_version, platform, namespace) plus every trap with its per-trap signature stripped. This binds the set of traps together: adding, removing, or editing any trap (even with a forged per-trap signature) invalidates the document signature.

Canonicalization. Before signing, the relevant object is serialized deterministically as compact JSON with sorted keys:

canonical = json.dumps(obj, sort_keys=True, separators=(",", ":"))
signature = hmac.new(key, canonical.encode(), hashlib.sha256).hexdigest()[:32]

Verification recomputes the canonical form and compares signatures in constant time.

Key handling. The HMAC key is supplied by the caller — via the DMLSigner(key) constructor or DMLSigner.from_env("DML_KEY"). The library contains no default, fallback, or hardcoded key; a missing key raises rather than signing with a guessable value. Keep the key secret: anyone who has it can forge valid signatures.

Limitations

  • HMAC-SHA256, not asymmetric. Signatures use HMAC-SHA256 (symmetric key), not public-key cryptography. Anyone who holds the signing key can forge signatures. Key distribution and rotation are outside DML's scope — treat the key as a shared secret scoped to a deployment environment.
  • No encryption. DML signs documents but does not encrypt them. Trap definitions are visible to anyone who can read the file. If your trap definitions themselves are sensitive (e.g., they reveal internal network paths), sign and then encrypt at the transport or storage layer.
  • Canonicalization is JSON-only. The deterministic serialisation used for signing is compact JSON with sorted keys. Documents stored in YAML are converted to JSON before signing, so the on-disk format and the signed representation differ. This is transparent to the library but matters if you verify signatures outside Python.
  • No automatic key rotation. DML has no built-in key lifecycle. Rotating the key requires re-signing every document — plan your key rotation process separately.
  • The spec is not a runtime. DML describes and signs traps; it does not deploy, manage, or enforce them. Integration with sensors, aggregators, and response systems is the responsibility of the platform consuming the DML document (e.g., WraithWall).

License

MIT — see LICENSE. Copyright (c) 2026 Niffyhunt.


Part of the WraithWall project — https://wraithwall.online · by Niffyhunt.

Metadata

Release files for dml-spec 1.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for dml-spec 1.1.0
File Size Uploaded Provenance Actions
dml_spec-1.1.0.tar.gz 22.9 kB Without provenance Details for dml_spec-1.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for dml-spec 1.1.0
File Interpreter ABI Platform Actions
dml_spec-1.1.0-py3-none-any.whl Python 3 none any Details for dml_spec-1.1.0-py3-none-any.whl

Total release size: 43.1 kB

Release files / dml_spec-1.1.0.tar.gz

Download URL dml_spec-1.1.0.tar.gz
Size 22.9 kB
Tags Source
SHA-256 checksum
How to use checksums
27f2329feed91117d75c46b485b506294a27f012bf0acfdbc9fa8cbb785b4bcf
BLAKE2b-256 checksum
How to use checksums
4720b3f98eebda0de549eb6fc4cb208f6bcb38a9ff2e22652d09e43bc50dc069
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.3

Release files / dml_spec-1.1.0-py3-none-any.whl

Download URL dml_spec-1.1.0-py3-none-any.whl
Size 20.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
482eea2c134248a400203bb5ddd65e4e2231d2f551121279bced8157c0658043
BLAKE2b-256 checksum
How to use checksums
8aa486410105cf9096a2aa8b07d6bed622d15062160f4e24269f266b4d96c785
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.3

Security for dml-spec 1.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

1.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page