From 58915d81158c45bb9c65cd61c3c11fe4befe21f1 Mon Sep 17 00:00:00 2001 From: anti Date: Sun, 10 May 2026 04:27:14 -0400 Subject: [PATCH] =?UTF-8?q?docs:=20add=20BEHAVE-SHELL=20reference=20page?= =?UTF-8?q?=20=E2=80=94=20all=2037=20primitives,=20attribution=20state=20m?= =?UTF-8?q?achine,=20calibration=20data?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BEHAVE-SHELL.md | 627 ++++++++++++++++++++++++++++++++++++++++++++++ Fingerprinting.md | 75 ++---- _Sidebar.md | 1 + 3 files changed, 643 insertions(+), 60 deletions(-) create mode 100644 BEHAVE-SHELL.md diff --git a/BEHAVE-SHELL.md b/BEHAVE-SHELL.md new file mode 100644 index 0000000..a916b53 --- /dev/null +++ b/BEHAVE-SHELL.md @@ -0,0 +1,627 @@ +# BEHAVE-SHELL + +BEHAVE-SHELL is DECNET's behavioural biometrics engine for interactive shell +sessions. It transforms raw PTY recordings into 37 attribution primitives +that fingerprint *how* an operator works — their motor patterns, cognitive +style, OPSEC habits, and emotional state — independently of what IP address +or tooling they use. + +The primitives feed the [Identity-Resolution](Identity-Resolution) attribution +state machine, which accumulates evidence across sessions to answer: *is this +the same hands?* + +--- + +## Design principles + +- **Pure extraction library.** `extract_session()` takes an iterable of + asciinema events and yields `Observation` envelopes. No I/O, no DB access, + no bus calls. The worker owns all side effects. +- **PII by design.** Command text is never stored in plain form — only the + SHA-256 of the first token is retained. Output is reduced to a byte count + and an error verdict. Prompt lines are ANSI-stripped and capped at 256 + characters. +- **Idempotent persistence.** `UniqueConstraint(evidence_ref, primitive)` + on the observations table means replaying a shard never duplicates rows. +- **Confidence capping.** Emotional-valence features carry a hard confidence + cap of 0.50 — they contribute, but never dominate an attribution decision. + +--- + +## Data flow + +``` +PTY session + │ + ▼ +sessrec.c — writes JSONL shard per session + │ {"sid": id, "t": ts, "ch": "i"|"o", "d": data} + │ Non-UTF-8 bytes handled via surrogateescape + ▼ +attacker.session.ended bus event + │ + ▼ +_handler.handle_session_ended() + │ Reads shard from disk → parse_shard_line() → AsciinemaEvent tuples + ▼ +build_session_context() (_ctx.py, ~573 lines) + │ Seven derivation steps (see below) + ▼ +extract_session() (extract.py) + │ Fan-out across 37 registered feature functions (FEATURES registry) + │ Each yields 0..N Observation envelopes + ▼ +Upsert ObservationRow → publish attacker.observation.* + │ + ▼ +attribution_worker (attribution_worker.py) + │ Consumes attacker.observation.> bus events + │ Runs aggregate() per (identity_uuid, primitive) + ▼ +AttributionStateRow state ∈ {unknown, stable, drifting, conflicted, multi_actor} +``` + +--- + +## Session context derivation + +`build_session_context()` performs a single-pass walk over the raw event +stream and produces a `SessionContext` that all 37 feature functions read. +The seven derivation steps, in order: + +| Step | What it computes | +|---|---| +| **Paste-burst detection** | Groups consecutive paste-class events (≥4 chars within 200 ms) into `paste_bursts` | +| **Typing-burst segmentation** | Splits the keystroke stream at think-pauses > 2.0 s into `typing_bursts[][]` (dropped if < 3 IATs) | +| **Correction signals** | Counts backspaces (`0x7f`, `0x08`) and kill-line sequences (`0x15`, `0x17`); records IATs between each backspace and the preceding keystroke | +| **Per-command intra-typing IATs** | For each command, extracts keystroke inter-arrival times from that command's span only | +| **Command segmentation** | Splits on `\r`/`\n`; per command records `first_token_hash` (SHA-256), tab count, readline shortcut count, and pipe count | +| **Inter-command IAT gaps** | Time between consecutive commands | +| **Error detection** | Scans output between commands for canonical error patterns (`"command not found"`, `"Permission denied"`, `"No such file"`) to set `command.errored` | +| **PS1 prompt detection** | Regex for `$`, `#`, `%`, `>` suffix after ANSI stripping; caps at 256 chars | +| **Keyboard layout fingerprinting** | Builds unigram and bigram histograms from typed letters | +| **Lexical counters** | Obscenity hits, positive/negative sentiment tokens, max caps run, max consecutive `!` run | + +### Key data structures + +``` +SessionContext + sid: str + t_start, t_end, duration_s: float + input_events, output_events: tuple[AsciinemaEvent] + iats: tuple[float] # inter-keystroke intervals + paste_bursts: tuple[PasteBurst] + typing_bursts: tuple[tuple[float]] + backspace_count, kill_line_count: int + intra_command_iats: tuple[tuple[float]] + commands: tuple[Command] + inter_cmd_iats: tuple[float] + prompt_lines: tuple[PromptLine] + typed_unigram_counts, typed_bigram_counts: Mapping[str, int] + typed_letter_count: int + obscenity_hits, positive_lex_hits, negative_lex_hits: int + caps_run_max, bang_run_max: int + +Command + start_ts, end_ts: float + first_token_hash: str # SHA-256 of first token only + tab_count, shortcut_count, pipe_count: int + errored: bool + output_bytes: int + followed_by_prompt: bool + +PromptLine + ts: float + suffix_char: str # $ # % > + raw_line: str # ANSI-stripped, ≤256 chars + is_root: bool +``` + +--- + +## The 37 primitives + +### Motor (9) — muscle memory and physical interaction style + +These primitives capture *how* an operator's fingers interact with the +keyboard — patterns that persist across sessions, accounts, and even +operating systems. + +#### 1. `input_modality` +Values: `typed` | `pasted` | `mixed` + +Ratio of paste events to total input events. ≥40 % pasted and ≤5 % +typed → `pasted`; ≤5 % pasted → `typed`; otherwise `mixed`. + +A script kiddie running pre-written one-liners pastes habitually. A +seasoned operator types most commands from memory. + +#### 2. `paste_burst_rate` +Values: `none` | `occasional` | `habitual` + +Coarser bucketing of the paste ratio. ≥50 % → `habitual`, +≥10 % → `occasional`. + +#### 3. `keystroke_cadence` +Values: `steady` | `bursty` | `hunt_and_peck` | `machine` + +Median coefficient of variation (CV) of within-burst inter-keystroke +intervals (IKIs). + +| CV | Mean IKI | Label | +|---|---|---| +| < 0.30 | < 30 ms | `machine` | +| < 0.45 | any | `steady` | +| < 0.70 | any | `bursty` | +| ≥ 0.70 | any | `hunt_and_peck` | + +`machine` catches automated input that passes as human visually but has +inhumanly uniform inter-key timing. + +#### 4. `motor_stability` +Values: `steady` | `variable` | `tremor` + +Fraction of IKIs below the tremor floor (30 ms). ≥20 % → `tremor` +(physiological or tool-simulated). Otherwise the median CV classifies +`steady` vs `variable`. + +#### 5. `error_correction` +Values: `immediate` | `deferred` | `absent` | `route_around` + +Timing of backspace relative to the preceding keystroke. Median ≤500 ms +→ `immediate` (noticed fast, muscle-memory correction). Median > 500 ms +→ `deferred` (reads output then corrects). Zero backspaces but kill-line +present → `route_around` (ctrl-u / ctrl-w). No corrections at all → +`absent`. + +#### 6. `command_chunking` +Values: `fluent` | `fragmented` | `single_command` + +Median CV of per-command intra-typing IKIs. < 0.40 → `fluent` (commands +typed as rehearsed phrases). Otherwise `fragmented`. Only one command +in session → `single_command`. + +#### 7. `shell_mastery.tab_completion` +Values: `none` | `occasional` | `habitual` + +Fraction of commands containing at least one `0x09` (tab) keystroke. +0 → `none`, < 50 % → `occasional`, ≥ 50 % → `habitual`. + +Operators who tab-complete heavily know the filesystem; those who never do +either memorise paths or are running a prepared script. + +#### 8. `shell_mastery.shortcut_usage` +Values: `none` | `moderate` | `heavy` + +Readline control-byte count (ctrl-a, ctrl-e, ctrl-r, etc.) per command. +< 0.05 → `none`, < 0.15 → `moderate`, ≥ 0.15 → `heavy`. + +#### 9. `shell_mastery.pipe_chaining_depth` +Values: `shallow` | `moderate` | `deep` + +Median pipe count per command. ≤1 → `shallow`, 2 → `moderate`, ≥3 → `deep`. + +--- + +### Cognitive (11) — decision-making and planning style + +These primitives capture *how* an operator thinks — their command repertoire, +response to failure, and how much they read output before acting. + +#### 10. `inter_command_latency_class` +Values: `instant` | `typing_speed` | `deliberate` | `llm_lightweight` | `llm_heavyweight` | `long` + +Median inter-command pause bucketed against calibrated thresholds: + +| Threshold | Label | What it suggests | +|---|---|---| +| ≤ 0.30 s | `instant` | Scripted or replay | +| ≤ 1.50 s | `typing_speed` | Commands prepared, typing only | +| ≤ 2.00 s | `deliberate` | Reads output before next command | +| ≤ 8.00 s | `llm_lightweight` | May be consulting a fast LLM / notes | +| ≤ 30.00 s | `llm_heavyweight` | Consulting a slow LLM or manual reference | +| > 30.00 s | `long` | Long pauses — possibly interrupted or cautious | + +`llm_lightweight` and `llm_heavyweight` were calibrated against Claude +Free (fast) and Claude (slow) assisted operator sessions — a novel class +of adversary DECNET is designed to detect. + +#### 11. `command_branch_diversity` +Values: `linear_playbook` | `adaptive_branching` | `unknown` + +Unique first-token / total command ratio. < 5 commands → `unknown`. +≥ 70 % unique → `linear_playbook` (each command is different — following +a prepared list). < 70 % → `adaptive_branching` (repeating tools, +iterating on a problem). + +#### 12. `feedback_loop_engagement` +Values: `closed_loop` | `fire_and_forget` | `unknown` + +Pearson correlation between per-command output bytes and the following +inter-command pause. r > 0.30 → `closed_loop` (pauses longer when there +is more output to read). Otherwise `fire_and_forget`. Requires ≥5 +command/output/pause triples. + +#### 13. `inter_command_consistency` +Values: `metronomic` | `variable` | `bimodal` + +CV of inter-command IKIs. < 0.40 → `metronomic` (scripts, beacons). +> 1.50 → `bimodal` (two distinct paces — often short commands interleaved +with long waits for a compile or download). Otherwise `variable`. + +#### 14. `cognitive_load` +Values: `low` | `medium` | `high` + +Composite score: mean of (intra-typing CV / 1.0, error rate, pause CV / 1.5). +< 0.33 → `low`, < 0.67 → `medium`, otherwise `high`. + +High cognitive load across multiple sessions on the same identity is a +signal of an operator working outside their comfort zone — new target OS, +unfamiliar tooling, or time pressure. + +#### 15. `exploration_style` +Values: `methodical` | `targeted` | `chaotic` + +`repetition_rate` = 1 − unique/total commands. +`backtrack_rate` = fraction of commands that jump back to a previously used +tool category. Backtrack ≥30 % → `chaotic`. Repetition ≥50 % → `targeted` +(narrow focus, known objective). Otherwise `methodical`. + +#### 16. `planning_depth` +Values: `deep` | `reactive` | `shallow` + +`deep_pause_frac` = fraction of inter-command IKIs > 2.0 s. +`reactive_frac` = fraction ≤ 0.30 s. ≥40 % deep pauses → `deep`. +≥50 % reactive → `reactive`. Otherwise `shallow`. + +#### 17. `tool_vocabulary` +Values: `narrow` | `moderate` | `broad` + +Distinct first-token count (absolute). ≤3 → `narrow`, ≥10 → `broad`. + +#### 18. `error_resilience.retry_tactic` +Values: `retry_same` | `pivot` | `fallback` + +Post-error behaviour: does the operator retry the same command, switch to +a different approach, or fall back to reconnaissance? Skipped if no errors +occurred in the session. + +#### 19. `error_resilience.frustration_typing` +Values: `low` | `moderate` | `high` + +Delta between median intra-IKI after an error vs. after a success. +< 10 % delta → `low`, < 30 % → `moderate`, ≥30 % → `high`. + +Fast typing after errors suggests frustration; slow typing suggests +deliberation. + +#### 20. `error_resilience.fallback_to_man` +Values: `present` | `absent` + +After an error, does the next command start with `man`, `help`, or `info`? +Skipped if no errors. `present` indicates an operator consulting +documentation — less automated, less rehearsed. + +--- + +### Temporal (4) — session rhythm and pacing + +#### 21. `session_duration` +Values: `short` | `medium` | `long` | `marathon` + +| Duration | Label | +|---|---| +| < 60 s | `short` — single recon or scan | +| < 600 s | `medium` — targeted interaction | +| < 3600 s | `long` — sustained operation | +| ≥ 3600 s | `marathon` — extended presence / slow-burn APT | + +#### 22. `escalation_pattern` +Values: `bursty` | `sustained` + +Dynamic window analysis (window width = max(10 s, duration / target)). +CV and zero-window fraction classify whether activity clusters into bursts +separated by idle periods, or maintains a consistent level throughout. + +#### 23. `landing_ritual` +Values: `cleanup` | `exploration` | `passive` + +First ~5 commands classified by intent tokens. `cleanup` if the operator +immediately starts removing evidence; `exploration` if they run +reconnaissance commands (`id`, `whoami`, `uname`, `ls`); `passive` if +they do nothing that reveals intent. + +#### 24. `exit_behavior` +Values: `cleanup` | `standard` | `anomalous` + +Last ~5 commands. `cleanup` if history/log deletion or `exit`/`logout` +appears. `anomalous` if the session ends abruptly with no recognisable +closing pattern. + +--- + +### Environmental (5) — operator's local setup + +These are stable across an operator's career and change only when they +switch machines or retool. + +#### 25. `shell_type` +Values: `bash` | `sh` | `zsh` | `fish` | `unknown` + +Detected from PS1 prompt regex patterns after ANSI stripping. + +#### 26. `terminal_multiplexer` +Values: `tmux` | `screen` | `none` + +Detected from PS1 markers and characteristic escape sequences. + +#### 27. `locale` +Values: `en-US` | `en` | `other` | `unknown` + +Language-specific keywords in prompt lines and error messages. + +#### 28. `keyboard_layout` +Values: `qwerty` | `dvorak` | `colemak` | `other` + +Bigram frequency analysis of the typed character stream. Operators who +touch-type on Dvorak produce a statistically distinct bigram distribution +that persists even when typing non-English commands. + +#### 29. `numpad_usage` +Values: `occasional` | `frequent` | `none` + +Keystroke pattern detection for numpad-originated digits. + +--- + +### Operational (4) — mission and OPSEC posture + +#### 30. `objective` +Values: `recon` | `exfil` | `persistence` | `lateral` | `destructive` + +Token-based intent classification of command first-tokens. Majority vote +across classified tokens; precedence order applied for ties. Skipped if +fewer than 3 classified tokens. + +Example token mappings: +- `recon`: `id`, `whoami`, `uname`, `cat`, `find`, `ls`, `ps`, `netstat` +- `exfil`: `scp`, `curl`, `wget`, `base64`, `nc`, `rsync` +- `persistence`: `crontab`, `echo`, `tee`, `systemctl`, `rc.local` +- `lateral`: `ssh`, `xfreerdp`, `psexec`, `wmiexec` +- `destructive`: `rm`, `shred`, `dd`, `mkfs`, `kill` + +#### 31. `opsec_discipline` +Values: `careful` | `learning` | `careless` + +Presence of history-disabling tokens (`unset HISTFILE`, `HISTSIZE=0`, +`history -c`) and cleanup activity in the session tail. Both → `careful`. +History-only → `learning` (knows to cover tracks but forgets cleanup). +Neither → `careless`. + +#### 32. `cleanup_behavior` +Values: `thorough` | `partial` | `none` + +Distinct cleanup tokens in the last 5 commands. ≥3 → `thorough`, +1–2 → `partial`, 0 → `none`. + +#### 33. `multi_actor_indicators` +Values: `solo` | `handoff_detected` + +Splits commands at the session's temporal midpoint and compares the median +intra-IKI of each half. If the delta exceeds 50 % and both halves have +≥4 commands, `handoff_detected` is emitted — the session was likely shared +between two operators (e.g. initial access handed to a post-exploitation +specialist). + +--- + +### Emotional valence (4) — stress and cognitive state + +These features have a hard confidence cap of **0.50** — they contribute to +attribution but cannot dominate it. They require ≥80 typed letters to emit. + +#### 34. `valence` +Values: `positive` | `neutral` | `negative` + +Lexical positive/negative token counts. `positive` if positive count > +(negative + obscenity) and ≥2 positive tokens. + +#### 35. `arousal` +Values: `low_calm` | `medium_engaged` | `high_agitated` + +`high_agitated` if ≥5 consecutive caps, ≥3 consecutive `!`, or fastest +IKI < 60 ms on ≥30 keystrokes. `low_calm` if slowest IKI > 300 ms. +Otherwise `medium_engaged`. + +#### 36. `stress_response` +Values: `none` | `eustress_positive` | `distress_negative` + +Post-error vs baseline typing speed ratio. ≥1.20 → `eustress` (types +faster under pressure — experienced). ≤ 1/1.20 → `distress` (types +slower — less experienced or genuinely stressed). + +#### 37. `frustration_venting` +Values: `low` | `moderate` | `high` + +Post-error frustration token count plus obscenity count. + +--- + +## Attribution state machine + +Primitives feed a per-`(identity_uuid, primitive)` state machine in +`decnet/correlation/attribution/aggregate.py`. + +### States + +| State | Meaning | Condition | +|---|---|---| +| `unknown` | Insufficient data | < 3 observations | +| `stable` | Consistent value | Recent N agree AND no drift from older N | +| `drifting` | Recently changed | Recent N agree BUT differ from older N | +| `conflicted` | Contradictory values | Recent N are split (high CV) | +| `multi_actor` | Multiple operators | `conflicted` + cross-session alternation | + +Window size N = 5 (categorical primitives). EWMA is used for numeric +primitives (Phase 3). + +### Multi-actor detection + +The attribution worker runs a `_multi_actor_tick` every 60 seconds. For +every `(identity, primitive)` pair in `conflicted` state, it checks whether +the alternation pattern across sessions is consistent with a credential +being shared between two distinct operators. When ≥2 primitives +independently flag `multi_actor` for the same identity, the bus emits: + +``` +attribution.profile.multi_actor_suspected + {identity_uuid, primitives: [...], evidence_summary, confidence, ts} +``` + +`confidence` is capped at 0.60 — cross-primitive agreement is the real +signal, but a hard cap prevents over-alarming on noisy primitives. + +--- + +## Database tables + +### `ObservationRow` + +One row per `(evidence_ref, primitive)`. `evidence_ref` is the session +shard identifier — the `UniqueConstraint` makes re-processing idempotent. + +| Column | Type | Description | +|---|---|---| +| `id` | UUID PK | | +| `identity_uuid` | FK → `attacker_identities` | | +| `attacker_uuid` | FK → `attackers` | Direct link for pre-clusterer path | +| `evidence_ref` | TEXT | Shard ID | +| `primitive` | TEXT | e.g. `keystroke_cadence` | +| `value` | TEXT | Categorical label or serialised numeric | +| `confidence` | FLOAT | 0.0–1.0 | +| `observed_at` | DATETIME | Session end time | + +### `AttributionStateRow` + +One row per `(identity_uuid, primitive)`. Updated by the attribution +worker each time a new observation arrives. + +| Column | Type | Description | +|---|---|---| +| `identity_uuid` | FK → `attacker_identities` | | +| `primitive` | TEXT | | +| `state` | TEXT | `unknown`/`stable`/`drifting`/`conflicted`/`multi_actor` | +| `current_value` | TEXT | Most recent or EWMA value | +| `confidence` | FLOAT | | +| `observation_count` | INT | Total observations aggregated | +| `last_observation_ts` | DATETIME | | + +--- + +## Key thresholds + +All calibration constants live in `decnet/profiler/behave_shell/_thresholds.py` +(416 lines). The values below are the defaults; they can be overridden per +deployment without touching feature code. + +| Constant | Value | Used by | +|---|---|---| +| `PASTE_MIN_CHARS_PER_EVENT` | 4 | Paste detection | +| `PASTE_BURST_MAX_IAT_S` | 0.20 | Paste burst grouping | +| `MODALITY_PASTED_MIN` | 0.40 | `input_modality` | +| `CV_STEADY_MAX` | 0.45 | `keystroke_cadence` | +| `TREMOR_FAST_FLOOR_S` | 0.030 | `motor_stability` | +| `IKI_THINK_MAX_S` | 2.0 | Typing-burst split | +| `INTER_CMD_INSTANT_MAX` | 0.30 s | `inter_command_latency_class` | +| `INTER_CMD_LLM_LIGHTWEIGHT_MAX` | 8.0 s | LLM-assisted detection | +| `INTER_CMD_LLM_HEAVYWEIGHT_MAX` | 30.0 s | LLM-assisted detection | +| `BRANCH_DIVERSITY_LINEAR_MIN` | 0.70 | `command_branch_diversity` | +| `FEEDBACK_CORRELATION_MIN` | 0.30 | `feedback_loop_engagement` | +| `PAUSE_CV_METRONOMIC_MAX` | 0.40 | `inter_command_consistency` | +| `PAUSE_CV_BIMODAL_MIN` | 1.50 | `inter_command_consistency` | +| `SESSION_DURATION_SHORT_MAX` | 60 s | `session_duration` | +| `SESSION_DURATION_MEDIUM_MAX` | 600 s | `session_duration` | +| `SESSION_DURATION_LONG_MAX` | 3600 s | `session_duration` | +| `MIN_OBSERVATIONS_FOR_STATE` | 3 | Attribution state machine | +| `CATEGORICAL_WINDOW_N` | 5 | Attribution window | +| `MULTI_ACTOR_TICK_SECS` | 60 | Multi-actor tick | +| `EMOTIONAL_VALENCE_CONFIDENCE_CAP` | 0.50 | All `emotional_valence` features | + +--- + +## Calibration + +The system was calibrated against five behavioural classes across 15 sessions +(424 total observations): + +| Class | Sessions | Observations | Description | +|---|---|---|---| +| `HUMAN` | 1 | 34 | Human operator, no assistance | +| `YOU-sim` | 2 | 59 | Human-simulated scripted attacker | +| `LW-sim` | 5 | 136 | Lightweight LLM-assisted operator | +| `CLAUDE-FF` | 3 | 84 | Claude (fast/free tier) assisted | +| `CLAUDE-CL` | 4 | 111 | Claude (standard tier) assisted | + +All classes emit ≥27 distinct primitives (pass threshold). + +The `inter_command_latency_class` thresholds `llm_lightweight` (≤8 s) and +`llm_heavyweight` (≤30 s) were derived from timing measurements of these +sessions — DECNET can distinguish a human-with-fast-LLM from an unassisted +human in a single session with moderate confidence, and with high confidence +across 3+ sessions. + +--- + +## Testing + +```bash +# Offline smoke test — 5 shards, mock bus, must emit ≥27 distinct per class +scripts/behave_shell/smoke.sh + +# Live round-trip — replay calibration shards through a running DECNET +scripts/behave_shell/replay_calibration.py +``` + +--- + +## File reference + +``` +decnet/profiler/behave_shell/ + __init__.py Public API: extract_session() + extract.py Entry point — fans out to FEATURES registry (51 lines) + _ctx.py SessionContext builder (573 lines) + _parse.py Asciinema JSONL parsing (272 lines) + _handler.py Bus subscriber — disk I/O, persistence, publish (235 lines) + _intent.py Token → intent classification (115 lines) + _thresholds.py All calibration constants (416 lines) + _features/ + __init__.py FEATURES registry — list of 37 functions (104 lines) + motor.py Primitives 1–9 (422 lines) + cognitive.py Primitives 10–20 (593 lines) + temporal.py Primitives 21–24 (237 lines) + environmental.py Primitives 25–29 (352 lines) + operational.py Primitives 30–33 (218 lines) + emotional_valence.py Primitives 34–37 (223 lines) + +decnet/correlation/ + attribution_worker.py Bus loop: consume observations, run tick + attribution/ + aggregate.py State machine: unknown→stable→drifting→conflicted→multi_actor + _thresholds.py Attribution-layer thresholds + +decnet/web/db/models/ + observations.py ObservationRow schema + attribution_state.py AttributionStateRow schema +``` + +--- + +## Related pages + +- [Fingerprinting](Fingerprinting) — all fingerprint layers, including the + BEHAVE-SHELL summary +- [Identity-Resolution](Identity-Resolution) — how observations are clustered + into attacker identities and how state machine transitions propagate +- [Service-Personas](Service-Personas) — enabling session recording and + BEHAVE-SHELL per service diff --git a/Fingerprinting.md b/Fingerprinting.md index d64d782..cc1ed29 100644 --- a/Fingerprinting.md +++ b/Fingerprinting.md @@ -178,69 +178,24 @@ Command content reveals intent directly: reconnaissance (`id`, `whoami`, persistence (`crontab -e`, `echo >> ~/.bashrc`), exfiltration (`curl`, `wget`, `base64`, `scp`). -#### Keystroke dynamics (BEHAVE-SHELL spec) +#### Keystroke dynamics (BEHAVE-SHELL) -The BEHAVE-SHELL spec (`decnet/profiler/behave_shell/`) extracts -fine-grained typing and session behaviour from the PTY stream. These -become **attribution primitives** — per-`(identity_uuid, primitive)` -state-machine entries that accumulate evidence across sessions. +The BEHAVE-SHELL engine (`decnet/profiler/behave_shell/`) extracts **37 +attribution primitives** across six domains from the PTY stream: motor +(typing cadence, error correction, shell mastery), cognitive (planning depth, +feedback loop engagement, tool vocabulary), temporal (session duration, +escalation pattern, landing and exit rituals), environmental (shell type, +keyboard layout, terminal multiplexer), operational (objective, OPSEC +discipline, multi-actor detection), and emotional valence (stress response, +frustration venting). -**Motor patterns** (muscle memory, latency): +Each primitive feeds a per-`(identity_uuid, primitive)` state machine +(`unknown → stable → drifting → conflicted → multi_actor`). When two or +more primitives independently reach `multi_actor`, an +`attribution.profile.multi_actor_suspected` bus event fires. -| Primitive | Description | -|---|---| -| `interarrival_mean_sec` | Mean time between keystrokes/commands | -| `interarrival_p75_sec`, `interarrival_p99_sec` | Tail latency — distinguishes human from bot | -| `flow_rate_cmd_per_sec` | Command execution rate | -| `burst_event_count` | Clustering in time (burst size) | -| `typing_speed_wpm` | Estimated words per minute | -| `error_correction_ratio` | Backspace and correction frequency | - -**Cognitive patterns** (decision-making): - -| Primitive | Description | -|---|---| -| `command_error_rate` | Failure-command ratio | -| `retry_on_failure_ratio` | Persistence on error | -| `command_redo_rate` | Repeating the same failed command | -| `pipeline_breadth`, `pipeline_depth` | Command composition style | -| `distinct_tools_used` | Toolkit diversity per session | -| `tool_switch_frequency` | How often the operator changes tool | -| `verbose_flag_usage` | `-v`/`-vv` flag frequency (confidence proxy) | - -**Temporal patterns** (working hours, rhythm): - -| Primitive | Description | -|---|---| -| `activity_hour_of_day_entropy` | Consistency of working hours | -| `activity_day_of_week_entropy` | Weekly routine | -| `session_duration_p50_sec`, `p95_sec` | Session length distribution | -| `gaps_between_sessions_p50_sec` | Rest period / tool pacing | - -**Environmental patterns** (operator setup): - -| Primitive | Description | -|---|---| -| `shell_type` | bash / sh / zsh / fish / etc. | -| `environment_vars_entropy` | Degree of environment customisation | -| `working_directory_volatility` | Directory-jumping frequency | -| `tty_capabilities` | Terminal rows, cols, and `$TERM` value | - -**Operational patterns** (technique selection): - -| Primitive | Description | -|---|---| -| `privilege_escalation_attempts` | `sudo` / `su` frequency | -| `lateral_movement_attempts` | SSH/RDP connection attempts | -| `data_exfiltration_indicators` | `scp`, `curl`, `wget`, `base64`, `zcat` | -| `credential_access_attempts` | Greping for passwords, SSH key files | -| `persistence_technique_count` | Crontab edits, `.bashrc` modifications | - -Each primitive has a state machine: `unknown → stable → drifting → -conflicted → multi_actor`. When two or more primitives independently flag -`multi_actor` (e.g. two distinct shell types alternating per session), -an `attribution.profile.multi_actor_suspected` bus event fires — a strong -indicator of a shared credential or a compromised operator account. +See **[BEHAVE-SHELL](BEHAVE-SHELL)** for the complete primitive reference, +computation logic, calibration data, and thresholds. --- diff --git a/_Sidebar.md b/_Sidebar.md index 1a3e40d..baaa82a 100644 --- a/_Sidebar.md +++ b/_Sidebar.md @@ -51,6 +51,7 @@ - [Testing-and-CI](Testing-and-CI) - [Campaign-Clustering](Campaign-Clustering) - [Identity-Resolution](Identity-Resolution) +- [BEHAVE-SHELL](BEHAVE-SHELL) - [Performance-Story](Performance-Story) - [Tracing-and-Profiling](Tracing-and-Profiling)