feat(canary): fingerprint_html + fingerprint_svg generators
Two new synthesised-artifact generators that bake the obfuscated fingerprint payload into plausible-looking decoy files: * fingerprint_html — a mundane "Internal Asset Directory" page with a small table of fake hosts; the obfuscated payload is inlined at the bottom of <body>. Visible content (row pool slice, sync timestamp) also varies per mint via SHA-256-derived stable ints, so two extracted canaries don't diff to zero even on the rendered surface. * fingerprint_svg — standalone SVG with an embedded <script> CDATA block. SVG <script> only fires for top-level loads / <object> / <iframe>; <img>-referenced renders are safely inert. Both derive the mint UUID via uuid.uuid5 from the callback token, so re-mints are byte-identical (preserving the generator determinism contract) AND the same token produces the same mint UUID across HTML and SVG variants — the worker can correlate beacons across artifact shapes. Wired into the factory + KNOWN_GENERATORS, default placement paths under ~/Documents/asset_directory.html and ~/Documents/network_topology.svg for both linux and windows personas. Tests cover determinism, per-token divergence, structural validity (DOCTYPE/SVG headers), and that the beacon URL stays inside the obfuscated string array (not in plaintext). The two new entries skip in test_generators.py when Node toolchain is absent so bare CI checkouts still pass.
This commit is contained in:
@@ -9,12 +9,31 @@ the artifact" property.
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from decnet.canary import CanaryContext, get_generator
|
||||
from decnet.canary.factory import KNOWN_GENERATORS
|
||||
|
||||
# fingerprint_* generators shell out to javascript-obfuscator via Node.
|
||||
# Skip those parametrized cases when the toolchain isn't installed so a
|
||||
# bare CI checkout doesn't fail before `npm install` runs.
|
||||
_NEEDS_NODE = {"fingerprint_html", "fingerprint_svg"}
|
||||
|
||||
|
||||
def _node_toolchain_ready() -> bool:
|
||||
if shutil.which("node") is None:
|
||||
return False
|
||||
canary_dir = Path(__file__).resolve().parents[2] / "decnet" / "canary"
|
||||
return (canary_dir / "node_modules" / "javascript-obfuscator").is_dir()
|
||||
|
||||
|
||||
def _maybe_skip(name: str) -> None:
|
||||
if name in _NEEDS_NODE and not _node_toolchain_ready():
|
||||
pytest.skip(f"{name} requires node + javascript-obfuscator")
|
||||
|
||||
|
||||
def _ctx(**kw) -> CanaryContext:
|
||||
defaults = dict(
|
||||
@@ -29,6 +48,7 @@ def _ctx(**kw) -> CanaryContext:
|
||||
|
||||
@pytest.mark.parametrize("name", KNOWN_GENERATORS)
|
||||
def test_generator_is_deterministic(name: str) -> None:
|
||||
_maybe_skip(name)
|
||||
g = get_generator(name)
|
||||
a = g.generate(_ctx())
|
||||
b = g.generate(_ctx())
|
||||
@@ -184,5 +204,7 @@ def test_artifacts_carry_notes() -> None:
|
||||
# check what we did before the file lands. Empty notes would mean
|
||||
# the operator is staring at opaque bytes.
|
||||
for name in KNOWN_GENERATORS:
|
||||
if name in _NEEDS_NODE and not _node_toolchain_ready():
|
||||
continue
|
||||
art = get_generator(name).generate(_ctx())
|
||||
assert art.notes, f"{name} produced no notes"
|
||||
|
||||
Reference in New Issue
Block a user