3 Commits

Author SHA1 Message Date
af615f8d44 Merge release/1.1.1: ttp test fixes (v1.1.1)
Corrects stale confidence_max ceiling tests + documented topics set.
Test-only patch, no production change.
2026-06-18 19:23:48 -04:00
d1974ca6f6 release: bump to v1.1.1 (test-only patch)
Corrects stale confidence_max ceiling tests + documented-topics set.
No production code change.
2026-06-18 19:23:39 -04:00
a26dfe4d47 fix(ttp): correct stale clip tests to ceiling semantics + document ATTACKER_FINGERPRINTED topic
confidence_max is a ceiling (min(base, ceiling)), not a multiplier — the
ASVS pass fixed this (BUG-8: min(base, base*ceiling) -> min(base, ceiling)),
but 4 lifter clip tests still encoded the old base*ceiling math (0.45/0.4/
0.35) and were masked by the make test-web bundle error fail-fast. All four
now assert the 0.5 ceiling. Separately, test_topics_matches_documented_set
lacked attacker.fingerprinted, which worker.py legitimately subscribes to
(JARM/HASSH/tcpfp/ipv6_leak -> TTP tagging). Located via turbovec + git pickaxe.

(cherry picked from commit f83b467c35649a06fa36f4b350e6666379cd71cb)
2026-06-18 19:22:54 -04:00
7 changed files with 25 additions and 9 deletions

View File

@@ -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/), 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). 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 ## [1.1.0] - 2026-06-18
Worker consolidation: cut the long-running worker fleet's resident memory by Worker consolidation: cut the long-running worker fleet's resident memory by

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "decnet" name = "decnet"
version = "1.1.0" version = "1.1.1"
description = "Deception network: deploy honeypot deckies that appear as real LAN hosts" description = "Deception network: deploy honeypot deckies that appear as real LAN hosts"
readme = "README.md" readme = "README.md"
authors = [{ name = "Samuel Paschuan", email = "samuel.paschuan@xmartlab.com" }] authors = [{ name = "Samuel Paschuan", email = "samuel.paschuan@xmartlab.com" }]

View File

@@ -220,11 +220,12 @@ def test_clipped_state_caps_confidence() -> None:
out = asyncio.run(lifter.tag( out = asyncio.run(lifter.tag(
_ev("session", {"beacon_interval_s": 60, "beacon_jitter_pct": 0.05}), _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 # Base confidences in YAML are 0.8 and 0.85; a clipped state caps each
# → 0.4 and 0.425 respectively. # 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 assert out
for tag in 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: def test_expired_state_treated_as_disabled() -> None:

View File

@@ -179,8 +179,8 @@ def test_clipped_rule_caps_confidence() -> None:
"credential_hash": "x", "reuse_count": 3, "credential_hash": "x", "reuse_count": 3,
}))) })))
assert len(out) == 1 assert len(out) == 1
# Base 0.9 × 0.5 ceiling. # Base 0.9 capped at the 0.5 ceiling — min(0.9, 0.5).
assert out[0].confidence == pytest.approx(0.45) assert out[0].confidence == pytest.approx(0.5)
# ── Ownership predicate ───────────────────────────────────────────── # ── Ownership predicate ─────────────────────────────────────────────

View File

@@ -114,8 +114,8 @@ def test_clipped_rule_caps_confidence() -> None:
payload = {"shared_password_hash": "x", "account_count": 9} payload = {"shared_password_hash": "x", "account_count": 9}
out = asyncio.run(lifter.tag(_ev(payload))) out = asyncio.run(lifter.tag(_ev(payload)))
assert len(out) == 1 assert len(out) == 1
# Base confidence 0.9 × 0.5 ceiling clamp. # Base confidence 0.9 capped at the 0.5 ceiling — min(0.9, 0.5).
assert out[0].confidence == pytest.approx(0.45) assert out[0].confidence == pytest.approx(0.5)
def test_expired_rule_does_not_fire() -> None: def test_expired_rule_does_not_fire() -> None:

View File

@@ -423,9 +423,11 @@ def test_clipped_intel_rule_caps_confidence() -> None:
"abuseipdb_score": 100, "abuseipdb_score": 100,
"abuseipdb_categories": [18], "abuseipdb_categories": [18],
}))) })))
# Bases are 0.60.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 assert out
for tag in 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) ─ # ── Decoupling guard (behavioral counterpart of E.2.7 static check) ─

View File

@@ -170,6 +170,7 @@ def test_topics_matches_documented_set() -> None:
_topics.attacker(_topics.ATTACKER_SESSION_ENDED), _topics.attacker(_topics.ATTACKER_SESSION_ENDED),
_topics.attacker(_topics.ATTACKER_OBSERVED), _topics.attacker(_topics.ATTACKER_OBSERVED),
_topics.attacker(_topics.ATTACKER_INTEL_ENRICHED), _topics.attacker(_topics.ATTACKER_INTEL_ENRICHED),
_topics.attacker(_topics.ATTACKER_FINGERPRINTED),
_topics.identity(_topics.IDENTITY_FORMED), _topics.identity(_topics.IDENTITY_FORMED),
_topics.identity(_topics.IDENTITY_MERGED), _topics.identity(_topics.IDENTITY_MERGED),
_topics.credential(_topics.CREDENTIAL_REUSE_DETECTED), _topics.credential(_topics.CREDENTIAL_REUSE_DETECTED),