# security-gym Roadmap

Phased development plan for the security-gym Gymnasium environment.

## Phase 1 — Foundation ✅

Package skeleton, data layer, auth_log parser, event/hashed feature extractors, target builder, SecurityLogStreamEnv. 58 tests passing, `gymnasium.utils.check_env` passes.

## Phase 2 — Alberta Integration ✅

`SecurityGymStream` adapter (`adapters/scan_stream.py`), GitHub Actions CI (test + lint + security). 86 tests passing.

## Phase 3 — Parsers + Wrappers ✅

syslog, web_access, web_error, journal parsers; SessionFeatureExtractor (20-dim); HashedFeatureWrapper, SessionAggregationWrapper, WindowedWrapper, DecayingTraceWrapper; enriched env info dict (event_type, src_ip, username). 172 tests passing.

## Phase 4 — Attack Scripts ✅

YAML-driven campaign framework, MITRE ATT&CK-aligned phases, AttackModuleRegistry (recon/ssh_brute_force/log4shell/credential_stuffing/ssh_post_auth/redis_lua_escape), non-stationary timing profiles, IPManager (spoofed + aliased), LogCollector (SSH/SFTP), CampaignLabeler (time+IP matching), auditd parser, CampaignOrchestrator, CLI (`python -m attacks`). 6 attack modules covering 6 MITRE ATT&CK tactics (Reconnaissance, Credential Access, Initial Access, Execution, Discovery, Command and Control) with kill chain campaign support.

## Phase 5 — Data Collection ✅

Generate labeled attack datasets by running campaigns against the Isildur VM.

- Isildur VM fully configured (researcher user, SSH key + password auth, NOPASSWD sudo for ausearch)
- Docker stack deployed (Log4Shell on :8080, Nginx reverse proxy on :80)
- All log sources verified readable (auth_log, syslog, nginx access/error, journal)
- LogCollector auto-detects target timezone via SSH (`date +%z`), corrects BSD syslog timestamps
- SSH brute force campaign: 1,124 events (230 malicious, 894 benign)
- Log4Shell campaign: 169 events (151 malicious, 18 benign)
- Recon SYN scan campaign: 712 events (19 malicious, 693 benign)
- Combined 3-phase campaign (recon → SSH → Log4Shell): 1,029 events (868 malicious, 161 benign)
- Credential stuffing campaign: 361 events (197 malicious, 164 benign)
- Post-auth execution campaign: 591 events (237 malicious, 354 benign)
- Full kill chain campaign (recon → cred stuffing → post-auth): 1,156 events (562 malicious, 594 benign)
- Benign DB imported from 3 servers (1.45M events), scrubbed of identifying hostnames/domains/emails
- v1 experiment streams composed: 7d brute-only, 30d heavy, 90d mixed, 365d realistic (136k events, 897 campaigns)
- v2 experiment streams re-composed with eBPF: 7d (26k events, 6 campaigns), 30d (510k, 284), 90d (475k, 277), 365d (1.64M, 897)
- Dataset published to GitHub Releases and Zenodo (DOI: 10.5281/zenodo.18810299)
- `security-gym` CLI for dataset download (`security-gym download`, `security-gym list`)

## Bugfix — event_type serialization & session enrichment (2026-03-04)

Fixed `event_type` being lost during EventStore serialization — the auth_log parser was the only parser that didn't store `event_type` in `fields["event_type"]`. Also added a safety net in EventStore to inject `event_type` into the parsed JSON if any parser omits it. Additionally, the auth_log parser now enriches PAM session open/close events with `src_ip` and `session_id` by caching PID→(ip, session_id) from preceding auth events. This unblocks rlsecd's `GymEventStoreSource` which needs `event_type` to distinguish auth_success from auth_failure without pattern-matching workarounds.

## Phase 6 — Experiments (Future)

Connect security-gym to alberta-framework and run continual learning experiments.

- Feed SecurityGymStream into MultiHeadMLPLearner
- Compare IDBD / Autostep / LMS optimizers on security log data
- Evaluate session vs. event vs. hashed feature representations
- Benchmark against chronos-sec honeypot results
- Non-stationarity analysis (concept drift across campaign phases)
- Kill chain cross-phase prediction: can agents learn that recon predicts execution?

## Open Design Question — Attack Type Encoding

The attack_type head (Head 1) currently uses **ordinal compression**: 8 categorical types mapped to a single float in [0, 1] via `index / 7`. This creates a false distance metric — MSE loss treats brute_force→credential_stuffing (error 0.02) as a smaller mistake than brute_force→exfiltration (error 1.0), even though these are unrelated categories. In practice, ObGD gradient capping and strong shared-trunk features mitigate this, but it's architecturally unsound.

The problem deepens at MITRE ATT&CK scale. The current 8-type taxonomy is a simplified subset; the full framework has ~200 techniques across 14 tactics, and grows with each ATT&CK release. Any encoding must handle this scale and remain stable as new techniques are added.

### Options

1. **One-hot (N neurons, cross-entropy loss)** — Standard classification. Clean separation, no false distances. But 200+ output neurons is expensive, every new technique requires retraining the output layer, and the encoding carries zero information about technique relationships.

2. **Learned embeddings (fixed-dim vector per technique)** — Each technique gets a dense vector (e.g., 16-dim) learned during training. Techniques with similar behavioral signatures converge to nearby embeddings. Scales better than one-hot, but still requires a growing lookup table and retraining when new techniques appear.

3. **Hierarchical encoding (tactic + technique, two heads)** — Encode MITRE tactic (14 categories) and technique-within-tactic separately. Mirrors ATT&CK's own structure. Tactically meaningful — confusing two techniques within the same tactic is a smaller error than confusing tactics. Scales to new techniques within existing tactics without changing the tactic head.

4. **Property-based encoding (behavioral features, not IDs)** — Encode observable attack *properties* rather than technique identity: network vs. host, lateral vs. vertical, noisy vs. stealthy, automated vs. manual, pre-auth vs. post-auth, etc. Fixed-dimension output regardless of technique count. A novel technique the agent has never seen still has recognizable properties. Most aligned with how human analysts think and with continual learning goals (generalization over memorization).

### Note: attack_stage is intentionally ordinal

Unlike attack_type, the attack_stage head (Head 2) has genuine sequential structure — the kill chain is a real progression (recon → initial_access → execution → persistence → exfiltration). Confusing adjacent stages is a smaller conceptual error than confusing distant ones, so the ordinal encoding's distance metric is meaningful here rather than arbitrary. Attackers can skip stages (e.g., stolen credentials → straight to post-auth host recon), but skipping stages doesn't break the ordering — it just means not every attack traverses every stage. Stages are assigned per-event based on campaign phase, so parallel kill chains (recon on host A while exfiltrating from host B) are already handled correctly.

### Current Decision

Keep ordinal compression for Phase 6 experiments. The 8-type taxonomy is a stepping stone; investing in one-hot now is throwaway work if the eventual answer is option 3 or 4. The ordinal baseline also provides a comparison point for whatever encoding replaces it. Revisit before Phase 8 (NetFlow) when the attack taxonomy will need to expand.

## Phase 7 — Streaming Server (Future)

Internet-facing service that serves composed streams to remote agents. Wraps SecurityGymStream with a network protocol (gRPC/WebSocket), authentication, and rate limiting. Enables multiple researchers and agents to consume the same composed stream without local DB access.

## Phase 8 — NetFlow Data (Future)

Extend security-gym with benign and attack NetFlow data generation. Attack traffic will likely be encrypted (TLS/SSH tunnels), making payload inspection useless — the learning agent must detect threats from flow metadata alone (packet sizes, timing, byte counts, duration, flags). This is an interesting test of whether continual learning agents can pick up on subtle distributional signatures in metadata when content is opaque.

- NetFlow v5/v9 or IPFIX collector and parser
- Benign flow generation (normal web browsing, DNS, software updates, etc.)
- Attack flow generation (C2 beacons, exfiltration over encrypted channels, lateral movement)
- Flow feature extractor (bytes/packets per flow, duration, inter-flow timing, port entropy)
- Ground-truth labeling for flow data (time+IP matching, same as log campaigns)
- StreamComposer support for mixed log + NetFlow experiment streams
- Evaluate whether metadata-only features are sufficient for detection without payload inspection

## Phase 9 — Kernel Event Telemetry ✅

Hook the RL agent directly into the Linux kernel for real-time observation of system-level events. Moves beyond log parsing to native kernel telemetry — the agent observes syscalls, process creation, file access, and network connections as they happen. This is the path toward an agent that can detect and respond to threats at the OS level rather than after the fact in log files.

- [x] eBPF-based event collection daemon (`server/ebpf_collector.py`, BCC tracepoints)
- [x] eBPF event parser and orchestration wrapper (`attacks/collection/ebpf_collector.py`)
- [x] eBPF labeling fix — kernel events during attack windows correctly labeled malicious via CampaignLabeler
- [x] All campaign YAMLs updated with `ebpf: {enabled: true, baseline_seconds: 30}`
- [x] Benign eBPF baseline collection script (`scripts/collect_ebpf_baseline.py`)
- [x] BUILD.md updated with BCC install, sudoers, V2 snapshot procedure
- [x] Composition configs updated for v2 databases (benign_v2.db, campaigns_v2.db)
- [x] Install BCC on Isildur (manual .deb install for kernel 5.10)
- [x] Sudoers rules for researcher (eBPF collector, pkill, python3)
- [x] Fix BPF compilation for Debian 11 / BCC 0.18 (kernel includes, `bpf_probe_read_kernel` for task_struct)
- [x] Fix orchestrator: Ed25519 key, sudo/nohup ordering, SSH transport keepalive
- [x] Collect benign eBPF baseline (997 events from ~1 hour manual collection)
- [x] Re-run all 7 campaigns with eBPF collection — 48,520 events (24,774 malicious, 23,746 benign)
- [x] Validate v2 labels — 5 PASS, 1 SKIP, 3 FAIL (all known/expected: eBPF spot-check, temporal order, session coherence)
- [x] Fix label validator crash (check 5: target array consistency — `collect_numpy` returns list of dicts, not numpy array)
- [ ] Create ISILDUR_READY_V2 snapshot on Frodo
- [x] Re-compose experiment streams from v2 databases — 4 streams with eBPF kernel events
- [x] Enrich eBPF event lines: PPID + parent_comm on process events, UID on network events
- Extended 24-hour benign eBPF baseline from Isildur (capture cron jobs, log rotation, Docker health checks, diurnal patterns) with synthetic benign traffic (legitimate SSH sessions, web browsing through nginx)
- Periodic kernel state summary as text (active PIDs, open sockets, privileged process count, new procs/conns per window) — injected into the event stream as a text line, not structured numerics, so the agent learns its own encoding. Revisit after Phase 6 baselines show whether the agent struggles with event-rate signals in raw text.
- Attack campaigns that generate kernel-level telemetry (privilege escalation, rootkits, fileless malware)
- Real-time streaming from kernel to agent (low-latency path, not log-based)
- Integration with existing log + NetFlow streams for multi-modal observation
- Evaluate agent performance on kernel events vs. log-only vs. combined

## PyPI Publication

Publish `security-gym` to PyPI as a `0.x` alpha package (API may change). Packaging audit completed — source code and config are ready, only build artifacts need refreshing.

**Already done:**
- `pyproject.toml` metadata (description, classifiers, license, keywords, URLs)
- Hatch build config targets `src/security_gym` only (attacks/server/scripts excluded from wheel)
- CLI entry point (`security-gym` → download/list commands)
- Gymnasium env auto-registration via entry point
- Optional dependency extras (alberta, attacks, collection, dev, all)
- No hardcoded paths — all defaults overridable

**Remaining steps:**
- [x] Add `src/security_gym/py.typed` marker (empty file for type checker discovery)
- [x] Delete stale `dist/` (v0.1.0 wheels, missing 9 source files added since)
- [x] Bump version if needed (`git tag --sort=-v:refname | head -5` to check) — already at v0.3.0
- [x] Rebuild: `python -m build` — v0.3.0 wheel verified
- [x] Test install from wheel in clean venv, verify imports + CLI + `gym.make()`
- [x] GitHub Actions publish workflow (OIDC trusted publishing: build → TestPyPI → PyPI on `v*` tags)
- [x] Configure `testpypi` and `pypi` environments in GitHub repo settings (trusted publisher)
- [x] Publish to TestPyPI (v0.3.0 test, v0.3.1 full pipeline)
- [x] Publish to PyPI — v0.3.1 live at https://pypi.org/project/security-gym/

## Phase 9b — Redis CVE-2022-0543 Attack Module

Added `redis_lua_escape` attack module — exploits the Debian-specific Lua sandbox escape in Redis (CVSS 10.0) for unauthenticated RCE. Three-stage attack: Redis enumeration (INFO, CONFIG, DBSIZE, CLIENT LIST), Lua sandbox escape via `EVAL` + `package.loadlib()`, and post-exploitation system commands. Raw TCP/RESP protocol (no new dependencies). Six command profiles (system_info, user_enum, network_enum, redis_enum, persistence, full_recon).

- [x] `redis_lua_escape` attack module (`attacks/modules/redis_lua_escape.py`)
- [x] RESP protocol helpers (encode, read, command) — inline, ~80 lines
- [x] Campaign configs: `redis_exploit_only.yaml` (standalone), `redis_killchain.yaml` (recon → Redis → SSH pivot)
- [x] Tests: registration, RESP encoding, command profiles, dry-run, mocked execute, campaign YAML loading (30 total, all pass)
- [x] `server/BUILD.md` updated with Redis setup and CVE-2022-0543 documentation
- [x] Install Redis on Isildur (`apt install redis-server`, bind 0.0.0.0, protected-mode no)
- [x] Create ISILDUR_READY_V3 snapshot on Frodo
- [x] Run `redis_exploit_only` and `redis_killchain` campaigns against Isildur
- [x] campaigns_v2.db now 60,468 events (30,436 malicious, 30,032 benign) — +~12K from Redis campaigns
- [x] Labels validated: 5 PASS, 1 SKIP, 3 FAIL (all pre-existing/expected: eBPF spot-check, temporal order, session coherence)
- [x] Campaign YAMLs updated from `en0` (macOS) to `enp3s0` (Hopper/Linux) for all 8 aliased-strategy configs
- [ ] Re-compose experiment streams with Redis attack data

## Phase 10 — Benign Data Rebuild (v3)

Rebuild the benign dataset from scratch to eliminate hospital PII (LHSC/SJHC staff names, phone numbers, org names in URL query parameters) that survived the original hostname scrub. The prior Zenodo record was deleted.

- [x] Create `scripts/build_benign_v3.py` — generic, reproducible build tool accepting any server log tarballs via `--source NAME:PATH`
- [x] 9-stage pipeline: Prep → Extract → Parse → Filter → Scrub → Insert → eBPF carryover → Sort → Verify → Report
- [x] Malicious traffic filtering: web attacks (path traversal, SQLi, XSS, JNDI, RFI, scanner UAs, exploit paths, suspicious methods) + auth attacks (failed password, invalid user, preauth close, max auth attempts)
- [x] PII scrubbing via external JSON config (`--scrub-config`) or `--no-scrub` for logs without PII; case-insensitive replacements; default config maps all sources to campaign target (`isildur` / `192.168.2.201`) with `.internal` TLD domains
- [x] Hostname regex support for common-word hostnames that need contextual syslog-header-only replacement (can, dallas)
- [x] Update all 4 composition configs (`benign_v2.db` → `benign_v3.db`)
- [x] Automated verification: PII absence, attack content absence, source distribution, temporal order, all-benign check
- [x] Build report JSON (`data/build_benign_report.json`) with full audit trail for methodology reproducibility
- [x] Temporal sort stage — re-orders all events by timestamp after multi-server merge, rebuilds sequential IDs
- [x] Build complete: 4 servers (can, dallas, isildur, sak) → 7,915,858 events (4.96 GB), all 5 checks PASS
- [x] Re-compose experiment streams from benign_v3.db + campaigns_v2.db (sequentially to avoid OOM on 24GB system)
  - exp_7d_brute.db: 140K events (114K benign, 26K attack), 6 campaigns, 101MB
  - exp_30d_heavy.db: 1.0M events (494K benign, 509K attack), 284 campaigns, 643MB
  - exp01_90d.db: 1.9M events (1.4M benign, 475K attack), 277 campaigns, 1.3GB
  - exp_365d_realistic.db: 9.4M events (7.8M benign, 1.6M attack), 897 campaigns, 6.5GB
  - All 4 streams validated: temporal order PASS, label consistency PASS, session coherence PASS
- [ ] Publish v3 dataset to Zenodo (new DOI)
- [ ] Update README / CITATION.cff with new DOI

## Phase 11 — Analysis & Publication (Future)

Results analysis, dataset release, and dissertation integration.

- Statistical analysis of learning curves across optimizers
- Publication-quality figures via alberta-framework analysis tools
- Public dataset release (anonymized log streams with ground-truth labels)
- Dissertation chapter: security-gym as an Alberta Plan Step 6/8 domain
