Skip to content

Commit 6a433c9

Browse files
DavidJBiancoclaude
andcommitted
Update TODO.md with improvement loop findings (section 6.5)
Added 16 new findings from blind expert panel, marked 14 as resolved, documented 18 remaining tells with fix guidance and file locations. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 107ac11 commit 6a433c9

1 file changed

Lines changed: 140 additions & 7 deletions

File tree

TODO.md

Lines changed: 140 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# EvidenceForge Implementation Plan
22

3-
**Status:** Phase 6 - Expert-Identified Realism Fixes (44 findings from blind expert panel, 11 resolved)
3+
**Status:** Phase 6 - Expert-Identified Realism Fixes (44 original + 16 new from improvement loop, 46 resolved)
44
**Started:** 2026-03-11
5-
**Last Updated:** 2026-03-18 (Phase 6.1 P0 complete: 5 critical fixes, 702 tests passing)
5+
**Last Updated:** 2026-03-18 (Improvement loop complete: eval 75.4→90.6, 765 tests passing)
66
**Target MVP Completion:** 7-10 weeks from start
77

88
**Recent Completions:**
@@ -804,8 +804,8 @@
804804

805805
### 6.4 P3: Minor (Nice-to-Have Improvements)
806806

807-
- [ ] **Fix eCAR DNS FLOW protocol to UDP** (Assessment #33)
808-
- eCAR shows `protocol: "tcp"` for DNS while Zeek correctly shows UDP
807+
- [x] **Fix eCAR DNS FLOW protocol to UDP** (Assessment #33, Improvement Loop Iter 2)
808+
- eCAR now uses `"udp"` for DNS (port 53) and NTP (port 123) connections
809809
- Files: `activity.py` (`_emit_ecar_flow_event`)
810810
- [ ] **Add process attribution to eCAR FLOWs** (Assessment #34)
811811
- `pid: -1` on all FLOW events; real EDR tracks socket-to-process mapping
@@ -834,13 +834,146 @@
834834
- [ ] **Multiple answers for popular DNS domains** (Assessment #42)
835835
- Every query returns single answer; CDN domains return multiple A records
836836
- Files: `activity.py` (`_emit_dns_lookup`)
837-
- [ ] **Add `<Events>` root XML wrapper** (Assessment #43)
838-
- Standalone `<Event>` elements without containing root element
839-
- Files: `emitters/windows.py`
837+
- [x] **Add `<Events>` root XML wrapper** (Assessment #43, Improvement Loop Iter 2)
838+
- XML declaration + `<Events>` root + `</Events>` footer via header/footer templates
839+
- Files: `emitters/base.py`, `formats/format_def.py`, `formats/definitions/windows_event_security.yaml`
840840
- [ ] **Set AA flag for internal DNS** (Assessment #44)
841841
- Internal zone queries (corp.local) show AA: false; should be authoritative
842842
- Files: `activity.py` (`_emit_dns_lookup`)
843843

844+
### 6.5 Improvement Loop Findings (2026-03-18)
845+
846+
**Source:** Automated improvement loop with 4-expert blind panel on `insider-exfiltration` scenario.
847+
**Baseline:** Eval 75.4/100 → **Final: 90.6/100** across 5 iterations.
848+
849+
#### Resolved in improvement loop:
850+
- [x] **Zeek UID source port mismatch** (P0, Iter 2)
851+
- conn.log and dns.log had different `id.orig_p` for same UID
852+
- Fix: pass `src_port` through `generate_connection()` to dns.log emission
853+
- Files: `activity.py` (`generate_connection`, `_emit_dns_lookup`)
854+
- [x] **TCP orig_pkts inconsistent with history** (P0, Iter 2)
855+
- `orig_pkts: 1` with `history: "ShADadfF"` (implies 4+ orig packets)
856+
- Fix: derive TCP packet counts from history field characters
857+
- Files: `activity.py` (`generate_connection`)
858+
- [x] **External CDN hostnames resolve to internal IPs** (P0, Iter 3)
859+
- `cdn-3986.cloudfront.net``10.10.100.16` (impossible in reality)
860+
- Fix: internal IPs get internal hostnames (`srv-01.corp.local` pattern)
861+
- Files: `activity.py` (`_emit_dns_lookup`, `_generate_internal_hostname`)
862+
- [x] **Fabricated CDN/cloud hostnames** (P0, Iter 3)
863+
- `server-68-352.compute.amazonaws.com`, `cdn-NNNN.cloudfront.net` — wrong format
864+
- Fix: realistic formats (ec2-x-x-x-x, d{hash}.cloudfront.net, e{id}.dscb.akamaiedge.net)
865+
- Files: `activity.py` (`_generate_random_hostname`)
866+
- [x] **Reserved domains in DNS** (P1, Iter 3)
867+
- `api.example.com`, `blog.example.com`, `cdn.example.com`, `www.example.com`
868+
- Fix: replaced with real service domains in REVERSE_DNS map
869+
- Files: `activity.py` (REVERSE_DNS)
870+
- [x] **Same IPv6 for all AAAA queries** (P1, Iter 3)
871+
- 87% of AAAA responses used `2607:f8b0:` (Google prefix) regardless of provider
872+
- Fix: diverse prefixes per provider (Azure, Akamai, AWS, Cloudflare, Fastly)
873+
- Files: `activity.py` (`_ipv4_to_fake_ipv6`)
874+
- [x] **4625 SID contradiction with SubStatus** (P0, Iter 4)
875+
- SubStatus `0xc0000064` (user not found) paired with valid domain SID
876+
- Fix: NULL SID `S-1-0-0` for unknown-user failures; varied SubStatus types
877+
- Files: `activity.py` (`generate_failed_logon`)
878+
- [x] **4768:4769 TGT/TGS 1:1 ratio** (P0, Iter 4)
879+
- Exact same count (90,344 each) — statistically impossible
880+
- Fix: 2-5 TGS per TGT; TGS targets member servers (60%) not just DCs
881+
- Files: `engine.py` (`_generate_system_traffic`)
882+
- [x] **Zero ICMP traffic** (P0, Iter 5)
883+
- 250K conn.log entries with no ICMP at all
884+
- Fix: servers ping each other 0-1 times per hour
885+
- Files: `engine.py` (`_generate_system_traffic`)
886+
- [x] **NXDOMAIN `NonExistentSite` literal** (P2, Iter 2)
887+
- Placeholder string used as AD site name
888+
- Fix: replaced with `Default-First-Site-Name`
889+
- Files: `activity.py` (`_emit_dns_lookup`)
890+
891+
#### Eval scoring fixes (Iter 1):
892+
- [x] **FQDN hostname normalization** — eval VisibilityModel and all dimension scorers
893+
now handle both bare and FQDN hostnames (Source Correctness: 1→100)
894+
- [x] **PreAuthType integer coercion** — Windows parser now coerces to int (Parsability: 92→99.5)
895+
- [x] **Timezone-aware work hours** — eval compares in scenario timezone, not UTC
896+
- [x] **AD ports in common ports list** — Kerberos (88), LDAP (389) no longer flagged as anomalous
897+
- [x] **Deep off-hours anomaly detection** — only midnight-5am flagged, not all off-hours
898+
899+
#### Remaining tells (not yet addressed):
900+
901+
**P0 — Instant Giveaways:**
902+
- [ ] **RFC 5737 documentation IP (203.0.113.50) for exfiltration target**
903+
- TEST-NET-3 range instantly recognizable; also has `local_resp: true`
904+
- Fix: use realistic cloud storage IPs; fix `_is_private_ip()` for exfil destinations
905+
- Files: `activity.py` (storyline connection handling), scenario YAML
906+
- [ ] **No DNS resolution for exfiltration/storyline targets**
907+
- Connections to 203.0.113.50 (pCloud) have no corresponding DNS queries
908+
- Fix: emit DNS lookup before each storyline network connection
909+
- Files: `engine.py` (storyline execution), `activity.py`
910+
- [ ] **Kernel uptime counters non-monotonic in syslog**
911+
- Values jump randomly (1061731 → 516344 → 460933) instead of increasing
912+
- Fix: track per-host uptime state in StateManager, increment monotonically
913+
- Files: `engine.py` (syslog generation), `state_manager.py`
914+
- [ ] **EventRecordID timestamps go backward within same computer**
915+
- Higher RecordID has earlier timestamp (events shuffled then sequentially numbered)
916+
- Fix: sort events by timestamp before assigning EventRecordIDs per computer
917+
- Files: `emitters/windows.py` or `engine.py` (event ordering)
918+
- [ ] **No SSH/RDP connections in Zeek despite syslog SSH sessions**
919+
- Syslog shows active sshd but conn.log has zero port 22/3389 traffic
920+
- Fix: generate Zeek conn.log entries for SSH connections to Linux hosts
921+
- Files: `engine.py` (`_generate_system_traffic`)
922+
923+
**P1 — Expert-Level Tells:**
924+
- [ ] **No LogonType 5 (Service) events**
925+
- Zero events; real Windows servers constantly generate type 5 for services
926+
- Fix: emit 4624 type 5 for service accounts (svc*, $-suffix) in system traffic
927+
- Files: `engine.py` (`_generate_system_traffic`), `activity.py`
928+
- [ ] **No ANONYMOUS LOGON (S-1-5-7) events**
929+
- Real environments have frequent anonymous logons from network discovery
930+
- Fix: emit periodic 4624 type 3 with ANONYMOUS LOGON on servers/DCs
931+
- Files: `engine.py` (`_generate_system_traffic`)
932+
- [ ] **LmPackageName always "-" even for NTLM authentications**
933+
- Should be "NTLM V2" when AuthenticationPackageName is "NTLM"
934+
- Files: `activity.py` (`generate_logon`, `generate_failed_logon`)
935+
- [ ] **Zeek history "SS" is invalid**
936+
- Repeated SYN is still "S" in real Zeek; "SS" never appears
937+
- Fix: remove "SS" from `_TCP_CONN_ENTRIES` history patterns
938+
- Files: `activity.py` (`_TCP_CONN_ENTRIES`)
939+
- [ ] **Service distribution unrealistic** (too much Kerberos/LDAP, too little HTTPS)
940+
- Real enterprises have HTTPS as dominant protocol
941+
- Fix: increase baseline HTTPS connections, reduce Kerberos frequency
942+
- Files: `engine.py` (`_generate_system_traffic`)
943+
- [ ] **No 4770 TGT Renewal events**
944+
- TGTs expire every ~10 hours; 3-day window should have renewals
945+
- Fix: emit 4770 events periodically on DCs
946+
- Files: `activity.py` (new `generate_kerberos_renewal`), `engine.py`
947+
- [ ] **4672 Special Privileges ratio too low** (9% vs expected ~30%+)
948+
- Every admin/service/machine account logon should generate paired 4672
949+
- Fix: emit 4672 for machine accounts, service accounts, admin logons
950+
- Files: `activity.py` (`generate_logon`, `generate_machine_account_logon`)
951+
952+
**P2 — Polish Issues:**
953+
- [ ] **Events grouped by computer, not chronologically interleaved**
954+
- Windows XML has all EXEC-WS-01 events, then all EXEC-WS-02, etc.
955+
- Fix: sort events globally by timestamp before writing
956+
- Files: `emitters/windows.py` or `engine.py`
957+
- [ ] **Syslog only from 2 hosts** (DMZ-WEB-01, SRV-LOG-01)
958+
- 118-system environment should have more syslog sources
959+
- Fix: emit syslog for all Linux systems, or add forwarding from Windows
960+
- Files: `engine.py` (syslog target selection)
961+
- [ ] **No TXT DNS queries** (SPF/DKIM/DMARC checks)
962+
- Fix: add periodic TXT lookups for email-related domains
963+
- Files: `activity.py` (`_emit_dns_lookup`)
964+
- [ ] **Audit serial numbers non-monotonic in syslog**
965+
- Jump erratically instead of incrementing per boot
966+
- Fix: track per-host audit serial in StateManager
967+
- Files: `engine.py`, `state_manager.py`
968+
- [ ] **DNS-then-service paired pattern too uniform**
969+
- Every connection preceded by DNS with 10-50ms gap; needs caching, concurrency
970+
- Fix: skip DNS for cached domains, add temporal jitter
971+
- Files: `activity.py` (`_emit_dns_lookup`)
972+
- [ ] **Ground truth mislabels file servers as "C2 Servers"**
973+
- Internal SMB servers listed under "C2 Server" IOC category
974+
- Fix: use correct labels in ground truth generation
975+
- Files: `ground_truth.py`
976+
844977
**Phase 6 Milestone:** Expert panel re-review finds no P0 instant giveaways. P1 findings reduced by 50%+. Eval score ≥ 90.
845978

846979
---

0 commit comments

Comments
 (0)