Commit Graph

833 Commits

Author SHA1 Message Date
da2ad7a82a feat(dns): global upstream forward rate limit with sinkhole fallback
Adds DNS_FORWARD_BUDGET (default 50) and DNS_FORWARD_WINDOW (default 1.0s)
env vars. _can_forward() maintains a rolling deque of upstream call
timestamps; queries that exceed the budget within the window are answered
with the sinkhole (127.x) instead of being forwarded, making the honeypot
ineligible as a sustained amp vector even when real_recursive is enabled.
Rate limit is global (not per-source) so IP-spoofed amplification floods
hit the ceiling regardless of how many source addresses are rotated.
2026-05-21 20:50:20 -04:00
e5847b7e1e feat(dns): real recursive forwarding with sinkhole fallback
When DNS_REAL_RECURSIVE=true and DNS_ZONE_MODE=recursive, out-of-zone
queries are forwarded to DNS_UPSTREAM (default 8.8.8.8:53) via async
UDP. Upstream response is relayed as-is; on timeout or error the
already-computed sinkhole (127.x) is returned instead.

_handle() always runs first so logging, tunneling detection, flood
tracking, and recon-burst aggregation fire on every query regardless
of whether the response ultimately comes from upstream. _dispatch()
overlays forwarding on top of the sync handler.

Protocol handlers (UDP datagram_received, TCP session) are now async
via asyncio.ensure_future / await _dispatch(). Service class exposes
real_recursive (bool) and upstream (string) config fields.
2026-05-21 20:49:19 -04:00
8f33f1b849 fix(dns): recursive mode now returns sinkhole A answer, not NXDOMAIN
RA=1 + empty answer section is immediately detectable as fake by any
open-resolver scanner. Recursive mode now behaves like open mode
(127.0.0.x sinkhole, deterministic on qname) with RA=1 and AA=0,
matching what a real recursive resolver returns.
2026-05-21 20:40:27 -04:00
bbb126e435 feat(dns): fix three operational blind spots — flood detection, AAAA, recon burst
- Add per-src QPS counter (_qps_window) with flood_suspect event at ≥50 qps/10s;
  one event per src per 30s cooldown, does not suppress baseline query events.
- Add tracking_evicted telemetry every 100 LRU evictions so IP-rotation evasion
  of _txt_times/_qps_window/_recon_window is observable, not silent.
- Shared _track_lru helper consolidates LRU touch + eviction signalling across
  all three bounded OrderedDicts.
- Add TYPE_AAAA=28 support: _fake_ipv6() returns deterministic ULA (fd::/8)
  addresses for in-zone names; extra_records parser now accepts and validates
  AAAA entries via socket.inet_pton.
- Add per-src recon-burst aggregation (_recon_window): fingerprint_probe +
  zone_transfer + amp_probe are tracked per source in a 60s window; recon_burst
  fires when ≥2 distinct signal types seen, once per src per 120s cooldown.
- 47 tests passing (19 new across TestAAAARecords, TestFloodDetection, TestReconBurst).
2026-05-21 19:50:09 -04:00
77a466e615 feat(dns): add BIND-flavored DNS honeypot service
Python asyncio DNS server on UDP+TCP/53 masquerading as BIND 9.x.
Emits four event_type values: query, fingerprint_probe (version.bind /
hostname.bind / id.server CHAOS), zone_transfer (AXFR/IXFR, always
REFUSED), amp_probe (qtype=ANY or EDNS udp_size>1232), and
tunneling_suspect (long high-entropy labels or rapid TXT burst).

Zone persona is generated per-decky from instance_seed (domain name,
SOA serial, NS, A, MX, TXT SPF); overridable via config_schema.
Three zone modes: auth (default), recursive, open (sinkhole).
2026-05-21 19:07:49 -04:00
e292fd7d05 feat(web): surface bgp_prefix and rpki_status in AttackerDetail and export
AttackerData type gets bgp_prefix / rpki_status / rpki_source.
TimelineSection renders prefix inline next to AS number; RPKI status
shows as a green RPKI VALID / red RPKI INVALID badge, or dim
NO ROA for not-found. rpki-status-badge CSS added to Dashboard.css.
Export network block extended with the three new fields.
2026-05-21 16:17:38 -04:00
e1eda1e754 feat(profiler): wire enrich_rpki into _build_record
Import enrich_rpki from decnet.rpki and call it inline after the
ASN lookup. bgp_prefix, rpki_status, rpki_source added to the
record dict that feeds the Attacker upsert. enrich_rpki short-circuits
to (None, None) when asn is None, so private / unannounced IPs
never hit RIPE STAT.
2026-05-21 16:14:51 -04:00
49b4996956 feat(model): add bgp_prefix, rpki_status, rpki_source to Attacker
bgp_prefix (max 43 chars, indexed) holds the covering CIDR from
the ASN lookup. rpki_status / rpki_source hold RIPE STAT validation
outcome. All nullable — null means enrichment was skipped or ASN
did not resolve.
2026-05-21 16:13:31 -04:00
b799ade816 feat(rpki): ripestat validator + sqlite cache
RipeStatValidator makes two RIPE STAT calls per uncached IP:
network-info -> announced prefix, rpki-validation -> ROA state.
2-second timeout; any network failure returns status='unknown'.

SQLite cache keyed by IP, 12-hour TTL, pruned on validator init.
Cache avoids per-event HTTP for the high-churn attacker pool —
steady-state cost approaches zero for repeat offenders.
2026-05-21 16:13:01 -04:00
1a11287f76 feat(rpki): provider scaffold — base, factory, paths, ripestat skeleton
New decnet/rpki/ module mirrors decnet/asn/ shape. Validator ABC,
lazy singleton factory (DECNET_RPKI_PROVIDER=ripestat default),
paths.py with DECNET_RPKI_ROOT override. RipeStatValidator stub
returns 'unknown' unconditionally — HTTP wired in next commit.

enrich_rpki(ip, asn) -> (status, source) | (None, None); short-circuits
on DECNET_RPKI_ENABLED=false or asn=None.
2026-05-21 16:10:01 -04:00
e3d9908bed feat(asn): expose BGP prefix in AsnInfo and enrich_ip
Synthesize the covering CIDR at lookup time from the matched iptoasn
range using ipaddress.summarize_address_range. AsnInfo.prefix is
populated per-query; not persisted in the pickle cache.

enrich_ip now returns (asn, as_name, bgp_prefix, provider_name).
Profiler worker updated to unpack the 4-tuple and write bgp_prefix
into the attacker record dict.
2026-05-21 16:07:57 -04:00
2af46ed102 feat(ingester): promote icmp_error / icmp6_error probe fields to fingerprint bounties 2026-05-21 15:10:07 -04:00
3f8170be10 feat(prober): add Icmp6ErrorProbe — ICMPv6 error-leakage fingerprint
Four RFC 4443 stimuli (port-unreach, hop-limit-exceeded, unknown-NH,
bad-dest-option) produce a 4-char matrix + sha256 fingerprint for IPv6
attackers. Auto-registers via ActiveProbeMeta at priority=860 (after v4
icmp_error=850, before ipv6_leak=999). IPv4 targets fast-return None.
2026-05-21 15:03:10 -04:00
56229a272b feat(prober): add IcmpErrorProbe — ICMP error-leakage fingerprint
Sends four crafted stimuli (UDP/closed-port, TTL=1, DF+oversized,
bad IP option) and records which ICMP error classes come back, the
per-error RTT, and the bytes echoed in each ICMP body. Absence is
as informative as a reply — Linux rate-limiting is a fingerprint signal.

Returns None when no packets could be sent (no CAP_NET_RAW), so the
probe is a no-op in non-root test environments. Port-free ActiveProbe
subclass (priority=850), metaclass auto-registered in the registry.

Also fixes three sets of stale tests left over from the TlsCertProbe
migration (4b2759e0):
- test_active_probe_registry: closed name/order sets updated for
  tls_certificate and icmp_error
- test_prober_rotation: dead patches on worker.fetch_leaf_cert removed
- test_prober_worker (TestProbeCycleTLSCert): rewritten to test
  TlsCertProbe as an independent registry probe, patch target updated
  from worker.fetch_leaf_cert to probes.tlscert_probe.fetch_leaf_cert
2026-05-21 14:52:49 -04:00
4b2759e0fc refactor(prober): absorb TlsCertProbe into ActiveProbe registry
TLS cert capture was the last prober special-case that bypassed
ActiveProbeMeta. Moves logic into TlsCertProbe (priority=200, runs
after JARM) in probes/tlscert_probe.py; drops _capture_tls_cert,
the probe.probe_name=="jarm" name-check, and the direct
fetch_leaf_cert import from worker.py.
2026-05-21 14:32:07 -04:00
bd4700770b refactor(prober): generalise ActiveProbe registry to absorb Ipv6LeakProbe
ActiveProbe.run/syslog_fields/publish_payload now accept port=None so
non-port-iterating probes can live in the registry. Ipv6LeakProbe replaces
the hand-rolled _ipv6_leak_phase special case in worker.py; it runs last
via priority=999. _probe_cycle no longer has an ad-hoc phase call.

Fixes three stale test files (test_prober_bus, test_prober_rotation,
test_prober_worker) that were broken since the 916b21b6 registry refactor.
2026-05-21 14:27:48 -04:00
b80e621904 fix(prober): consolidate ip route get to single call + log bare excepts
_route_info() calls _ip_route_get once and returns (on_link, iface);
worker._ipv6_leak_phase now calls it instead of the two separate helpers.
Bare except clauses at _ip_route_get and response parse now log at debug.
2026-05-21 14:16:42 -04:00
1123e50325 fix(sniffer): add missing syslog_bridge.py to template build context 2026-05-20 22:22:47 -04:00
7bac3a29c6 fix(ingester): retry get_state on startup DB errors; bump deps + rename behave packages
ingester: wrap bootstrap get_state() in forever-retry loop — MySQL coming
up after the API process killed the ingestion task permanently before it
ever entered _run_loop. Regression test added.

deps: idna 3.13→3.15 (CVE-2026-45409), twisted 26.4.0rc2→26.4.0
(PYSEC-2026-160), pip 26.1→26.1.1 (CVE-2026-3219 resolved upstream),
behave-core/behave-shell renamed from decnet-behave-* and bumped to 0.1.1.
pre-commit hook updated to reflect current ignore list.
2026-05-20 22:10:15 -04:00
916b21b652 refactor(prober): ActiveProbe ABC + ActiveProbeMeta registry
Replace _jarm_phase / _hassh_phase / _tcpfp_phase boilerplate (3×~50
lines of identical port-iteration logic) with a metaclass-registered ABC.
Adding a new port-iterating active probe is now one class + three methods.

- decnet/prober/base.py: ActiveProbeMeta auto-registers subclasses by
  probe_name; ActiveProbe ABC enforces run/syslog_fields/publish_payload
  with env-driven DECNET_PROBE_PORTS_<NAME> port override.
- decnet/prober/probes/{jarm,hassh,tcpfp}.py: concrete probe classes.
- decnet/prober/worker.py: single _run_probe driver replaces the three
  phase functions; _probe_cycle iterates ActiveProbeMeta.all(); drops
  the ports=/ssh_ports=/tcpfp_ports= kwargs from prober_worker.
- IPv6 leak and TLS cert capture stay as special cases (different call
  shapes; intentionally outside the registry).
- tests/prober/test_active_probe_registry.py: registry contents, sort
  order, priority-10 override, ABC contract per probe class.
- tests/prober/test_run_probe_driver.py: dedup, success, None-skip,
  exception, rotation, publish paths for _run_probe.
- tests/prober/test_prober_worker.py: updated patch targets and
  _probe_cycle call sites; port control via monkeypatch.setattr.
2026-05-17 23:16:35 -04:00
3977f06374 feat(ttp/ipv6_leak): wire Ipv6LeakLifter into composite tagger and worker
- Add "ipv6_leak" to KNOWN_SOURCE_KINDS in ttp/base.py
- Register Ipv6LeakLifter(store) in factory.py get_tagger()
- Subscribe worker to attacker.fingerprinted; route by Event.type
  so JARM/HASSH/ipv6_leak share the topic without source_kind collision
- Add bump_attacker_ipv6_leak() to BaseRepository (abstract) +
  TTPMixin (implementation): increments ipv6_leak_count, sets last_ipv6_*
  denorm fields, appends-with-dedup to AttackerIdentity.ipv6_link_local_iids
- Call bump_attacker_ipv6_leak from _process_event after insert_tags
- Add DummyRepo stub + coverage call in tests/db/test_base_repo.py
2026-05-17 20:41:55 -04:00
11d9273c99 docs(bus): document ipv6_leak payload kind on ATTACKER_FINGERPRINTED
Add inline documentation for all known kind= discriminators on the
fingerprinted topic including the new ipv6_leak variant so future
consumers know what fields to expect without reading the prober source.
2026-05-17 20:22:55 -04:00
9056e33962 feat(ttp): Ipv6LeakLifter + R0059 rule for IPv6 link-local opsec failures
Ipv6LeakLifter subscribes to source_kind="ipv6_leak" events from both
the passive sniffer and active prober. Emits T1090 (Proxy) under TA0011
(C2) when fe80:: source address is observed — the attacker's VPN only
tunnels IPv4 so their link-local IID leaks their NIC identity.

Rule R0059 sets base confidence 0.85; iid_kind in the evidence carries
the per-observation strength (eui64 = MAC-derived, deterministic;
stable_privacy = RFC 7217; temporary = RFC 4941).
2026-05-17 20:22:26 -04:00
504340745e feat(prober): active IPv6 link-local solicitation phase
Add ipv6_leak.py with solicit_ipv6_leak() — sends ICMPv6 Echo to
ff02::1 on the attacker's iface and returns fe80:: evidence when a
link-local response arrives. Gated on _is_on_link(): skips when
attacker is behind a router (no L2 adjacency).

Add _ipv6_leak_phase() to worker.py (Phase 4 in _probe_cycle).
Phase runs once per attacker IP per cycle (sentinel at port 0 in
ip_probed["ipv6_leak"]) and publishes kind="ipv6_leak" via publish_fn.

Add list_v6_addrs(iface) to network.py: returns [(addr, scope)] for
all IPv6 addresses on an interface, required for source-routing ICMPv6
from the correct link-local address.
2026-05-17 20:20:19 -04:00
aa833ddda9 feat(sniffer): passive IPv6 link-local leak detection
Add _ipv6_iid_classify() to fingerprint EUI-64 vs stable-privacy IIDs
and derive the MAC OUI from EUI-64-encoded link-local addresses.
SnifferEngine._on_ipv6_packet() observes fe80::/10 sources destined for
known deckies and emits ipv6_link_local_leak syslog + bus events.
on_packet() now dispatches the IPv6 branch before the v4 TCP path.
BPF default widened from "tcp" to "tcp or ip6" so the sniff loop
captures IPv6 frames without config change.
2026-05-17 20:16:29 -04:00
69ecc4cc20 feat(models): add IPv6 link-local leak columns to Attacker + AttackerIdentity
Attacker gains five denormalized cache fields (ipv6_leak_count,
last_ipv6_leak_at, last_ipv6_link_local, last_ipv6_iid_kind,
last_ipv6_mac_oui) mirroring the rotation_count/last_rotation_at pattern.
AttackerIdentity gains ipv6_link_local_iids (JSON list[dict]) for
EUI-64-derived MAC cluster signals that survive VPN/IP rotation.
No ALTER TABLE helpers — direct SQLModel column additions per pre-v1 policy.
2026-05-17 20:12:08 -04:00
b390a35262 feat(ttp): add Ipv6LinkLocalLeakEvidence TypedDict + EVIDENCE_SCHEMA entry
Pins the evidence shape for IPv6 link-local leakage findings. All fields
optional (total=False) so partial observation (passive sniffer vs active
solicitation) fills whatever the vector provides. Lifter lands in a
subsequent commit.
2026-05-17 20:10:51 -04:00
3e6587e073 fix(lint): prefix unused params with _ to silence vulture 80% findings 2026-05-17 20:08:54 -04:00
6a91858c15 fix(https-template): wire TLS_CERT/TLS_KEY into make_server ssl_context
Server read the env vars but never passed them to make_server, so it
served plain HTTP and the TLS handshake check timed out in live tests.
2026-05-10 22:39:24 -04:00
a009746dd1 feat(fingerprint): extend syslog_bridge with HTTP/3 and JA4H fingerprinting emission 2026-05-10 22:27:22 -04:00
ff51ce55e2 fix(tests): eliminate tarpit OOM from global asyncio.sleep mock
Two interacting bugs caused asyncio.sleep to be mocked globally,
letting tarpit_watcher_worker spin the event loop on a non-async
mock and accumulate _increment_mock_call records without bound:

1. test_ingester.py patched `decnet.web.ingester.asyncio.sleep` via
   the asyncio singleton — any code in the process using asyncio.sleep
   (including the tarpit worker) hit the fake_sleep side_effect.
   Fix: add `_sleep = asyncio.sleep` alias in ingester.py and patch
   `decnet.web.ingester._sleep` instead — scopes the mock to ingester.

2. test_api_startup_guards.py called `_run_lifespan_startup` without
   DECNET_CONTRACT_TEST=true, which started the real tarpit task in a
   manually-constructed event loop that the tests never cancelled.
   Fix: set DECNET_CONTRACT_TEST=true inside _run_lifespan_startup so
   the lifespan skips all background workers.
2026-05-10 10:06:21 -04:00
9a7b03700c refactor(intel): migrate AttackerIntel JSON-string columns to native SQLAlchemy JSON
Five list columns (greynoise_tags, abuseipdb_categories, threatfox_threat_types,
threatfox_ioc_types, threatfox_malware_families) and four dict columns
(*_raw) are now Column(JSON) with list/dict type annotations and
default_factory=list/dict. Providers return native Python objects; the
application-layer json.dumps/json.loads round-trip and _decode_json_list
helpers are gone. to_intel_event_payload() reads columns directly.

Also caps pytest xdist at -n 4 and excludes tests/api from norecursedirs
to prevent schemathesis workers from OOM-killing the dev loop.
2026-05-10 09:17:15 -04:00
de3634d739 feat(ttp): enable 6 xfail tests — evidence shape + tracing spans
- test_evidence_shape.py: replace broken (command, BehavioralLifter)
  pairing with correct (http_fingerprint, HttpFingerprintLifter) case;
  expand _LIFTER_CASES to 5-tuples with per-lifter payloads and rule
  factories; wire StubRuleStore + _index.install() per lifter; remove
  xfail marker — all 4 parametrized cases now pass

- factory.py: add _span() helper gated on _telemetry._ENABLED; wrap
  each per-lifter dispatch in _tag_one() that opens a
  ttp.lifter.{name} child span per call

- http_fingerprint_lifter.py: add missing name = "http_fingerprint"

- test_tracing.py: replace pytest.fail() stubs in
  test_lifter_child_spans_emitted and test_no_pii_canary_in_span_attributes
  with real test bodies; remove xfail markers
2026-05-10 08:51:07 -04:00
6e7020f2aa feat(ttp): implement E.3.14b intel catch-up via attacker.session.ended
On every attacker.session.ended event, the TTP worker now reads the
persisted AttackerIntel row (if any) and synthesizes an intel-source
TaggerEvent so intel-derived tags emit even when attacker.intel.enriched
was dropped or arrived before the worker started.

Key changes:
- AttackerIntel.to_intel_event_payload() — single source of truth for
  the intel-row → lifter payload projection; shared by future callers
  without importing decnet.intel.* (no-SPOF contract preserved).
- BaseRepository.get_attacker_intel_row_by_uuid() — returns the live
  SQLModel instance so the catch-up path can call to_intel_event_payload().
- _build_intel_catchup_event() in ttp/worker.py — looks up the intel row,
  builds the TaggerEvent, returns None on absent row (silence, not error).
- _process_event() extended: appends the catch-up event to tagger_events
  when topic contains "session.ended". Deterministic source_id keeps
  compute_tag_uuid idempotent across replays; INSERT OR IGNORE deduplicates
  against any prior attacker.intel.enriched path.

DummyRepo stub + coverage call added per feedback_run_base_repo_test.md.
2026-05-10 08:27:22 -04:00
39518e33b4 feat(ttp): implement evidence-shape validation and confidence range constraint
- TolerantTagger.tag validates evidence keys against EVIDENCE_SCHEMA TypedDicts;
  TypeError (programmer error) propagates instead of being swallowed
- IntelEvidence and EmailEvidence expanded from stubs to full per-provider
  key sets (total=False); IntelEvidence old stub fields replaced wholesale
- EVIDENCE_SCHEMA map added to models/ttp.py and imported by base.py
- TTPTag __table_args__ gains confidence [0,1] CheckConstraint (DB-enforced)
- xfail removed from test_confidence_outside_range_rejected_at_insert and
  test_evidence_shape_violation_propagates_as_typeerror — both now pass
- TypeError removed from _SWALLOWED_EXCS fuzz list; test_intel_evidence_keys
  updated to assert the real provider key set
2026-05-10 07:56:52 -04:00
6fecf45dcd fix(orchestrator/tests): attribute access on TopologySummary, not dict
emailgen/scheduler.py: topology.email_personas/.language_default
test_heartbeat_topology_resync.py: row.needs_resync (5 occurrences)
2026-05-10 07:11:14 -04:00
4c8ef2f104 fix(orchestrator): _topology_personas accepts TopologySummary or dict 2026-05-10 07:08:39 -04:00
e4626879f6 perf(pytest): 194s → 4s collection — lazy heavy imports + norecursedirs
Four-part fix for the collection bottleneck that was blocking the dev loop:

1. Lazy mitreattack.stix20 import in attack_stix.py — deferred to first
   _load() call (TYPE_CHECKING guard at top level)

2. Lazy misp_stix_converter import in both MISP export routers — moved
   from module level into the route handler body

3. Lazy attack_catalog / attack_stix in ttp.py repo mixin — thin wrapper
   functions so the import chain never fires at module load time

4. tests/api/conftest.py — `from decnet.web.api import app` moved inside
   the `client()` fixture; `pytest_ignore_collect` broadened to skip all
   test_schemathesis*.py variants (not just test_schemathesis.py), which
   were launching a subprocess server at module-import time

5. pyproject.toml — `norecursedirs` for tests/live, tests/stress,
   tests/service_testing, tests/docker, tests/perf so these directories
   are never entered; `-m` filter removed from addopts (now redundant);
   `--dist loadscope` → `--dist load` to unblock workers immediately

6. behave_core / behave_shell rename — BEHAVE packages dropped the
   `decnet_` prefix; reinstalled editable installs and updated all 14
   import sites across profiler, ttp, bus, and correlation modules
2026-05-10 06:41:25 -04:00
967aec56d2 fix(bundle): prune node_modules during agent tarball walk 2026-05-10 05:17:32 -04:00
c2693aafc3 fix(clustering): filter extra fp keys before splatting into update_identity_fingerprints 2026-05-10 04:51:49 -04:00
f11def0af1 fix(collector): strip port from remote_addr before attacker identity resolution
host:port in remote_addr was creating a distinct Attacker row per TCP
connection instead of per IP. Split on the last ':' in parse_rfc5424;
preserve the port as fields['remote_port'] so repeated source ports are
retained as fingerprint signal in bounty payloads.
2026-05-10 04:06:42 -04:00
6a6f5807aa fix(pr3): adapt to quic-go v0.59.0 API — drop H3App, capture h3 SETTINGS via http3.Settingser
quic-go v0.59.0 (shipped with Caddy v2.11.2) removed quic.Connection as
a public interface and quic-go/logging as a public package, breaking
H3App's connection-wrapping approach.

Resolution:
- Remove H3App (h3app.go) entirely; Caddy handles h3 natively when h3
  is in the protocols list.
- Rewrite h3conn.go to keep only tryParseH3ControlStream + varint/name
  utilities (tested, useful for future stream-level tapping if the API
  ever re-exposes it).
- FPHandler.ServeHTTP: for h3 requests, type-assert ResponseWriter to
  http3.Settingser (the public interface exposed by quic-go/http3 v0.59),
  read the peer's Settings after ReceivedSettings channel closes, emit
  h3_settings fp record.
- https/entrypoint.sh: include h3 in CADDY_PROTOCOLS (Caddy now owns
  UDP/443); remove DECNET_H3_GLOBAL block.
- Update go.mod/go.sum to caddy v2.11.2 + quic-go v0.59.0.
- Update test_https_compose_h3_app.py to expect h3 in protocols when
  http/3 is selected, and assert decnet_h3 block is absent.
- All Go tests (9) and Python tests (15) remain green.
2026-05-10 03:43:34 -04:00
5675dd8ebc feat(pr3): canonical wire-order header capture for h1/h2 + H3App for SETTINGS
- Renames caddy.listeners.decnet_h2fp → decnet_fp; adds h1 raw-byte
  header capture (plainTappingConn) and h2 continuous HPACK decode loop
  (parseH2HeadersLoop) so headers_ordered reflects actual wire order, not
  Go map iteration order.
- Adds H3App Caddy module (decnet_h3) that owns UDP/443 via quic-go,
  wraps accepted QUIC connections with h3SettingsTappingConn to intercept
  the h3 control stream and extract RFC 9114 SETTINGS in wire order.
- Wires access_log emission from FPHandler.ServeHTTP via responseCapture.
- Updates syslog_bridge.py (canonical + per-service copies) with inline
  _compute_ja4h and new fp socket record branches: http_request_headers,
  h3_settings, access_log.
- Fixes ingester proto field alias (bridge emits 'proto', ingester expected
  'protocol') and exposes _process_fingerprint_bounties test alias.
- Go tests: h1/h2/h3 golden-byte tests all green; h3_tracer_test covers
  varint parser, GREASE detection, truncated-stream safety.
- Python tests: 15/15 green across bridge JA4H hash parity, ingester
  compat (old + new event shapes), and Caddyfile h3 template assertions.
2026-05-10 03:29:00 -04:00
8d1f26c0c7 fix(https): move Flask backend to 8443 to avoid netns conflict with http service on 8080 2026-05-10 02:31:08 -04:00
44ab42d80c fix(server): add from __future__ import annotations for Python <3.9 compat 2026-05-10 02:23:13 -04:00
d09b891a55 fix(syslog_bridge): add fp socket reader to canonical template — sync was overwriting per-service copies 2026-05-10 02:17:56 -04:00
42b5d97a50 fix(syslog_bridge): rewrite both templates with from __future__ annotations, fp socket imports, and start_fp_socket_reader 2026-05-10 02:06:53 -04:00
1669f25733 fix(syslog_bridge): add from __future__ import annotations for Python <3.9 compat 2026-05-10 01:58:43 -04:00
255ccebf29 fix(entrypoint): fail-fast if Flask does not bind within timeout instead of silently starting Caddy with no backend 2026-05-10 01:51:09 -04:00
d4f391bab1 fix(caddy): remove explicit tls from listener_wrappers — Caddy applies it by default 2026-05-10 01:45:03 -04:00