diff --git a/CHANGELOG.md b/CHANGELOG.md index 22eee06f..49fb736a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ All notable changes to DECNET are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.1.1] - 2026-06-18 + +### Fixed +- Test suite: corrected 4 lifter clip tests that encoded the pre-ASVS + `confidence_max` semantics (treating it as a `base × ceiling` multiplier). + `confidence_max` is a true ceiling — `min(base, ceiling)` — since the ASVS + hardening pass (BUG-8); the tests now assert the ceiling. They were masked by + the `make test-web` ATT&CK-bundle fail-fast. No production code change. +- `test_topics_matches_documented_set`: added `attacker.fingerprinted` to the + documented topic set — the TTP worker legitimately subscribes to it + (JARM/HASSH/tcpfp/ipv6_leak fingerprint results feed TTP tagging). + ## [1.1.0] - 2026-06-18 Worker consolidation: cut the long-running worker fleet's resident memory by diff --git a/pyproject.toml b/pyproject.toml index 4d4181c9..d252a35b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "decnet" -version = "1.1.0" +version = "1.1.1" description = "Deception network: deploy honeypot deckies that appear as real LAN hosts" readme = "README.md" authors = [{ name = "Samuel Paschuan", email = "samuel.paschuan@xmartlab.com" }] diff --git a/tests/ttp/test_behavioral_lifter.py b/tests/ttp/test_behavioral_lifter.py index 55ab1d48..cac35610 100644 --- a/tests/ttp/test_behavioral_lifter.py +++ b/tests/ttp/test_behavioral_lifter.py @@ -220,11 +220,12 @@ def test_clipped_state_caps_confidence() -> None: out = asyncio.run(lifter.tag( _ev("session", {"beacon_interval_s": 60, "beacon_jitter_pct": 0.05}), )) - # Base confidences in YAML are 0.8 and 0.85; clipped to 0.5 ceiling - # → 0.4 and 0.425 respectively. + # Base confidences in YAML are 0.8 and 0.85; a clipped state caps each + # at the 0.5 ceiling — min(base, 0.5) = 0.5. confidence_max is a ceiling, + # not a multiplier (BUG-8 in the ASVS hardening pass). assert out for tag in out: - assert tag.confidence < 0.5 + assert tag.confidence == pytest.approx(0.5) def test_expired_state_treated_as_disabled() -> None: diff --git a/tests/ttp/test_credential_lifter.py b/tests/ttp/test_credential_lifter.py index fc6d7728..a52aa9bf 100644 --- a/tests/ttp/test_credential_lifter.py +++ b/tests/ttp/test_credential_lifter.py @@ -179,8 +179,8 @@ def test_clipped_rule_caps_confidence() -> None: "credential_hash": "x", "reuse_count": 3, }))) assert len(out) == 1 - # Base 0.9 × 0.5 ceiling. - assert out[0].confidence == pytest.approx(0.45) + # Base 0.9 capped at the 0.5 ceiling — min(0.9, 0.5). + assert out[0].confidence == pytest.approx(0.5) # ── Ownership predicate ───────────────────────────────────────────── diff --git a/tests/ttp/test_identity_lifter.py b/tests/ttp/test_identity_lifter.py index b4d20e83..b0753410 100644 --- a/tests/ttp/test_identity_lifter.py +++ b/tests/ttp/test_identity_lifter.py @@ -114,8 +114,8 @@ def test_clipped_rule_caps_confidence() -> None: payload = {"shared_password_hash": "x", "account_count": 9} out = asyncio.run(lifter.tag(_ev(payload))) assert len(out) == 1 - # Base confidence 0.9 × 0.5 ceiling clamp. - assert out[0].confidence == pytest.approx(0.45) + # Base confidence 0.9 capped at the 0.5 ceiling — min(0.9, 0.5). + assert out[0].confidence == pytest.approx(0.5) def test_expired_rule_does_not_fire() -> None: diff --git a/tests/ttp/test_intel_lifter.py b/tests/ttp/test_intel_lifter.py index d6d81dd9..cb4cd26e 100644 --- a/tests/ttp/test_intel_lifter.py +++ b/tests/ttp/test_intel_lifter.py @@ -423,9 +423,11 @@ def test_clipped_intel_rule_caps_confidence() -> None: "abuseipdb_score": 100, "abuseipdb_categories": [18], }))) + # Bases are 0.6–0.7; a clipped state caps each at the 0.5 ceiling — + # min(base, 0.5) = 0.5 (confidence_max is a ceiling, not a multiplier). assert out for tag in out: - assert tag.confidence <= 0.35 + 1e-6 + assert tag.confidence == pytest.approx(0.5) # ── Decoupling guard (behavioral counterpart of E.2.7 static check) ─ diff --git a/tests/ttp/test_worker_bus.py b/tests/ttp/test_worker_bus.py index 47e0ac14..a475cc56 100644 --- a/tests/ttp/test_worker_bus.py +++ b/tests/ttp/test_worker_bus.py @@ -170,6 +170,7 @@ def test_topics_matches_documented_set() -> None: _topics.attacker(_topics.ATTACKER_SESSION_ENDED), _topics.attacker(_topics.ATTACKER_OBSERVED), _topics.attacker(_topics.ATTACKER_INTEL_ENRICHED), + _topics.attacker(_topics.ATTACKER_FINGERPRINTED), _topics.identity(_topics.IDENTITY_FORMED), _topics.identity(_topics.IDENTITY_MERGED), _topics.credential(_topics.CREDENTIAL_REUSE_DETECTED),