fix(report): one canonical Severity, one severity vocabulary, issen-owned anomaly codes - #13
Draft
h4x0r wants to merge 2 commits into
Draft
fix(report): one canonical Severity, one severity vocabulary, issen-owned anomaly codes#13h4x0r wants to merge 2 commits into
h4x0r wants to merge 2 commits into
Conversation
… squatting
Failing tests for three report-layer DRY defects. No implementation.
Defect 1 - two Severity clones duplicating forensicnomicon's:
- crates/issen-signatures/tests/severity_is_canonical.rs
- crates/forensic-pivot/tests/severity_is_canonical.rs
- crates/forensic-pivot/tests/severity_gains_canonical_ordering.rs
Both clones are the canonical five-tier scale, not a distinct native scale
(contrast srum-analysis's Clean/Informational/Suspicious/Critical, which
correctly normalizes at its boundary per ADR-0007), so both migrate onto
forensicnomicon::report::Severity rather than converting at a boundary.
Defect 2 - severity rank/token triplicated and already drifted:
- crates/issen-core/tests/severity_vocabulary.rs (the shared vocabulary)
- crates/issen-correlation/tests/severity_vocabulary_is_shared.rs
- crates/issen-report/tests/severity_vocabulary_is_shared.rs
The drift shipped: render_html emits class="severity-info" while the
stylesheet only defines .severity-informational, so the Info tier renders
unstyled; and Correlation::severity_from_str parses case-sensitively while
issen-report's copy is case-insensitive.
Defect 3 - issen mints codes inside ntfs-forensic's NTFS- namespace:
- crates/issen-correlation/tests/anomaly_code_namespace.rs
- crates/issen-disk/src/lib.rs (inline test)
RED output:
forensic-pivot severity_is_canonical (runtime)
thread 'severity_is_the_canonical_forensicnomicon_type' panicked at
crates/forensic-pivot/tests/severity_is_canonical.rs:17:5:
forensic-pivot must re-export forensicnomicon's Severity, not clone it
(got `forensic_pivot::rule::Severity`)
test result: FAILED. 1 passed; 1 failed
forensic-pivot severity_gains_canonical_ordering (compile)
error[E0369]: binary operation `>` cannot be applied to type `Severity`
note: `Severity` does not implement `PartialOrd`
issen-signatures severity_is_canonical (runtime)
thread 'severity_is_the_canonical_forensicnomicon_type' panicked at
crates/issen-signatures/tests/severity_is_canonical.rs:19:5:
assertion `left == right` failed: issen-signatures must re-export
forensicnomicon's Severity, not clone it
left: "issen_signatures::matching::results::Severity"
right: "forensicnomicon_core::report::Severity"
test result: FAILED. 1 passed; 1 failed
issen-core severity_vocabulary (compile)
error[E0432]: unresolved import `issen_core::severity`
error[E0599]: no method named `rank` found for enum `Severity`
error[E0599]: no method named `token` found for enum `Severity`
issen-correlation severity_vocabulary_is_shared (compile)
error[E0432]: unresolved import `issen_core::severity`
error[E0599]: no method named `token` found for enum `Severity`
issen-report severity_vocabulary_is_shared (compile)
error[E0432]: unresolved import `issen_core::severity`
--> crates/issen-report/tests/severity_vocabulary_is_shared.rs:10:17
| use issen_core::severity::{self, SeverityExt};
| ^^^^^^^^ could not find `severity` in `issen_core`
issen-correlation anomaly_code_namespace (runtime)
thread 'timestomp_code_is_not_in_the_ntfs_forensic_namespace' panicked:
`NTFS-` is ntfs-forensic's scheme; issen must mint under a prefix it owns
(got `NTFS-TIMESTOMP-SI-FN-MISMATCH`)
thread 'timestomp_code_does_not_shadow_ntfs_forensics_shipped_code' panicked:
a prefix-extension of a shipped code makes one detection look like two under
prefix grouping (got `NTFS-TIMESTOMP-SI-FN-MISMATCH`)
thread 'timestomp_code_is_minted_under_an_issen_owned_prefix' panicked:
expected an issen-owned prefix, got `NTFS-TIMESTOMP-SI-FN-MISMATCH`
test result: FAILED. 0 passed; 3 failed
issen-disk (runtime)
thread 'tests::boot_backup_code_is_not_in_the_ntfs_forensic_namespace'
panicked at crates/issen-disk/src/lib.rs:2128:9:
`NTFS-` is ntfs-forensic's scheme; issen must mint under a prefix it owns
(got `NTFS-BOOT-BACKUP-MISMATCH`)
test result: FAILED. 0 passed; 1 failed
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… issen-owned codes
Defect 1 - both Severity clones were pure duplicates, both migrated.
issen-signatures `Informational/Low/Medium/High/Critical` and forensic-pivot
`Critical/High/Medium/Low/Info` are the canonical five-tier scale under
different spellings/orderings, not distinct native scales, so neither warrants
an ADR-0007 boundary conversion. Both are now
`pub use forensicnomicon::report::Severity`.
- issen-signatures loses its private `from_str_lossy` and lowercase `Display`;
the bottom tier is `Info`, and the persisted token comes from the shared
vocabulary (`format!("{}", sev).to_lowercase()` in issen-cli::scanning still
yields the same lowercase string, now "info" rather than "informational").
- forensic-pivot gains `Ord` and `Display` it never had - pivot findings could
not be ranked at all before. It takes a `forensicnomicon` dependency with the
`serde` feature; the five variant names are unchanged so bundled rule YAML
keeps parsing.
- `#[non_exhaustive]` forces `_` arms in issen-navigator (tree marker, severity
colour), its alert mapper, and issen-cli's pivot table. Each escalates rather
than silently downgrading an unknown future tier.
Defect 2 - the rank/token/parse vocabulary now has one definition.
New `issen_core::severity`: `SeverityExt::{rank, token}`, `parse`,
`parse_lossy`, `TOKENS`. Deliberately a trait, so when forensicnomicon grows
inherent `Severity::rank()`/`token()` those shadow it and this module is a pure
deletion - no call site changes. That upstream move is a follow-up; a competing
forensicnomicon PR is open this session, so nothing is changed there.
Five copies collapse onto it: `severity_rank`/`severity_token` in issen-report
(kept as one-line call-site aliases, not a second definition),
`Correlation::severity_str`/`severity_from_str`,
`severity_from_finding_str`, `navigator_output::severity_of`, and
`Severity::from_str_lossy`.
Two drifts the copies had already shipped are fixed:
- `render_html` emitted `class="severity-info"` while the stylesheet only
defined `.severity-informational`, so the Info tier rendered unstyled.
- `Correlation::severity_from_str` was case-sensitive while issen-report's copy
was case-insensitive, and issen-disk writes a capitalised "High" into event
metadata. Both now parse case-insensitively.
The bottom-tier token moves from "informational" to "info". `"informational"`
stays accepted on every read path - `--min-severity`, the `scan_findings`
query filter (which also still matches rows persisted with the old token), and
the report parser - so pre-existing case DBs and invocations keep working.
`issen_timeline::query_findings` no longer hardcodes its own level ladder.
Defect 3 - the two codes describe related but genuinely different detections,
so they stay distinct; issen's moves out of ntfs-forensic's namespace.
ntfs-forensic owns `NTFS-` and already ships `NTFS-TIMESTOMP`, `NTFS-ADS`,
`NTFS-DELETED-RECORD`, `NTFS-SLACK-RESIDUE`, `NTFS-MFTMIRR-MISMATCH` and
`NTFS-LOGFILE-CLEARED`. issen consumes those codes, which makes it exactly the
crate that must not also mint into the namespace.
Reading both implementations, they are not one detection under two names:
ntfs-forensic works from parsed `$STANDARD_INFORMATION`/`$FILE_NAME`
attributes and fires on strict `$SI.created < $FN.created` OR any whole-second
`$SI` stamp, always grading High; issen's works from TimelineEvent metadata,
adds the `$SI.modified` ordering signal and a clock-skew tolerance, requires
the CONTRAST form of the sub-second tell (`$SI` zeroed while `$FN` retains
100 ns precision), and downgrades on benign-context modifiers - capping at
Medium by design as a low-confidence lead. Collapsing them onto one code would
make an Info lead indistinguishable from a High filesystem anomaly.
The real defect is that the old string was a prefix-extension of a shipped
code, so prefix grouping conflated them:
NTFS-TIMESTOMP-SI-FN-MISMATCH -> HEUR-TIMESTOMP-SI-FN
NTFS-BOOT-BACKUP-MISMATCH -> HEUR-BOOT-BACKUP-MISMATCH
`HEUR-` is issen's own detection-layer prefix (18 existing codes spanning
timestomping, location, entropy, size, magic and USN). Both codes are now
named constants (`TIMESTOMP_CODE`, `issen_disk::BOOT_BACKUP_CODE`) and asserted
through the constant rather than a literal, so they cannot drift again.
These are consumer-visible contract changes; the Case-001 Szechuan answers doc
is updated in the same commit. The `$Boot`-vs-backup check itself belongs
upstream in ntfs-forensic's `ArtifactAnomaly` family beside the `$MFTMirr` one
(ADR-0016 puts filesystem-structure checks in the FILESYSTEM layer) - a
cross-repo follow-up, noted in the constant's doc comment.
Gate: cargo build --workspace clean; cargo test for the nine affected crates
1887 passed / 0 failed; cargo clippy --workspace --all-targets -D warnings
clean; cargo fmt --check clean. One workspace test fails both here and on
origin/main - issen-mem `dispatch::tests::dispatch_windows_netstat_returns_ok`,
unrelated to this change and untouched by it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three report-layer DRY defects, fixed under strict TDD: RED commit
76a0109(failing tests only), GREEN commit499b055(implementation).Defect 1 — two
SeverityclonesBoth were checked against the possibility of being a genuinely different scale (as
srum-analysis'sClean/Informational/Suspicious/Criticallegitimately is, normalizing at its boundary per ADR-0007). Neither is — both are the canonical five-tier scale, so both migrate toforensicnomicon::report::Severityrather than converting at a boundary.issen-signatures::matching::results::SeverityInformationalis the canonicalInforenamed; plus a privatefrom_str_lossyand a private lowercaseDisplay.forensic-pivot::rule::SeverityPartialEq— so pivot findings could not be ranked at all. Now gainsOrdandDisplay.The
// RED: stub — types declared but no real logicheader onrule.rswas stale; that crate has real logic and is wired intoissen pivot.#[non_exhaustive]forces_arms inissen-navigator(tree marker, severity colour), its alert mapper, andissen-cli's pivot table. Each escalates rather than silently downgrading an unknown future tier.Defect 2 —
severity_rank/severity_tokentriplicatedInterim fix, issen-side only — as instructed, no competing
forensicnomiconPR was opened. Newissen_core::severityholds the one definition:SeverityExt::{rank, token},parse,parse_lossy,TOKENS. It is deliberately a trait, so whenforensicnomicongrows inherentSeverity::rank()/token()those shadow it and this module becomes a pure deletion with zero call-site churn.Five copies collapse onto it — the two named in the task, plus three more found while tracing:
severity_from_finding_str,navigator_output::severity_of, andSeverity::from_str_lossy.Two drifts the copies had already shipped, now fixed:
render_htmlemittedclass="severity-info"while the stylesheet only defined.severity-informational— the Info tier rendered unstyled.Correlation::severity_from_strparsed case-sensitively whileissen-report's copy was case-insensitive, andissen-diskwrites a capitalised"High"into event metadata.The bottom-tier token moves
"informational"→"info"."informational"stays accepted on every read path (--min-severity, thescan_findingsquery filter, the report parser), so pre-existing case DBs and invocations keep working.issen_timeline::query_findingsno longer hardcodes its own level ladder.Defect 3 —
NTFS-encroachmentThe two timestomp codes do not describe the same detection. Both implementations were read:
ntfs-forensicNTFS-TIMESTOMPNTFS-TIMESTOMP-SI-FN-MISMATCH$STANDARD_INFORMATION+$FILE_NAMEattributesTimelineEventmetadata$SI.created < $FN.created$SI.modified, plus a clock-skew tolerance$SIstamp, unconditional$SIzeroed while$FNretains 100 ns precisionHighInfo–Medium, capped atMediumby designSo consolidating onto
NTFS-TIMESTOMPwould make an Info-tier lead indistinguishable from a High-tier filesystem anomaly — losing the distinction issen's precision-first redesign exists to make. They stay distinct codes.The real defect is the namespace.
ntfs-forensicownsNTFS-and already ships six codes there (NTFS-TIMESTOMP,NTFS-ADS,NTFS-DELETED-RECORD,NTFS-SLACK-RESIDUE,NTFS-MFTMIRR-MISMATCH,NTFS-LOGFILE-CLEARED). issen consumes those codes, which makes it exactly the crate that must not also mint into the namespace — and the old string was a strict prefix-extension of a shipped code, so prefix grouping conflated the two.HEUR-is issen's own detection-layer prefix — 18 existing codes spanning timestomping, location, entropy, size, magic and USN. Both are now named constants (TIMESTOMP_CODE,issen_disk::BOOT_BACKUP_CODE), asserted through the constant rather than a literal, so they cannot drift again.These are consumer-visible contract changes. The Case-001 Szechuan answers doc is updated in the same commit.
Gate
cargo build --workspacecargo test(9 affected crates)cargo clippy --workspace --all-targets -- -D warningscargo fmt --checkOne workspace test fails both on this branch and on
origin/main:issen-memdispatch::tests::dispatch_windows_netstat_returns_ok. Verified againstmaindirectly; unrelated to this change and untouched by it.Follow-ups (cross-repo, not done here)
forensicnomicon— add inherentSeverity::rank()andSeverity::token().issen_core::severity::SeverityExtis shaped so those shadow it and the interim module deletes cleanly. Deferred because forensicnomicon PR #24 is open this session.ntfs-forensic— the$Boot-vs-backup integrity check belongs in itsArtifactAnomalyfamily beside the$MFTMirrone (ADR-0016 puts filesystem-structure checks in the FILESYSTEM layer). Once there, the crate legitimately owns anNTFS--prefixed code for it. Noted inBOOT_BACKUP_CODE's doc comment.issen-signatures'HEUR-TS-002($SI/$FNdivergence > 24 h) andHEUR-TS-003($SIsub-seconds zeroed while$FNretains precision) overlap the correlation-layer detector's S1/S3 signals. A third timestomp implementation, out of scope here.🤖 Generated with Claude Code