Skip to main content

security-gym

CI PyPI License Gymnasium Dataset DOI HF Dataset

Gymnasium-compatible replay environment for security defense research. Labeled log and kernel event streams recorded from a live vulnerable host are replayed without episode boundaries. The agent observes raw text (like tail -N on log files and kernel event channels) and takes defensive actions (block, throttle, alert, isolate) that causally suppress future observations.

Security-Gym is trace-driven: the telemetry is recorded, not produced by a dynamics model. The simulated component is the defense layer (blocklist, throttle list, isolation mode), which masks the stream in response to the agent's actions. The loop is therefore closed on observability but not on adversary behavior, since blocking an attacker suppresses that attacker's events while the recorded attacker does not change tactics in response. Throughout this repository, "environment" refers to the software and its Gymnasium API, "corpus" or "dataset" to the released v4.1 data, and "benchmark" to the evaluation protocol together with the baseline results in Baselines.

Built for the Alberta Plan vision of long-lived agents that continually learn from non-stationary sensory streams.

Security-Gym environment interaction loop

The continual-RL interaction loop. Telemetry sources (system logs and eBPF kernel events) are encoded as a Dict observation under one of two registered modes (Text or Hybrid). A streaming continual learner (no replay buffer; predict-then-update) emits a Dict action pairing a discrete defensive response with a continuous risk score, which updates a shared defense state (blocklist, throttle list, isolation mode). The dashed feedback edge captures the central difficulty of the domain: defensive actions causally suppress future observations on a continuous, non-episodic stream (terminated = False). The vector source is at figures/env-schematic.tex.

Dataset access — The v4 dataset is mirrored on HuggingFace (j-klawson/security-gym-v4, 2.8 GB compressed, 7d/30d/90d streams) and on Zenodo (10.5281/zenodo.18901541, 11.3 GB compressed, full release including the 365-day stream). MLCommons Croissant 1.0 metadata with Responsible AI fields ships at data/croissant.json.

Features

  • Raw text observations (v1) — 6 text channels (auth_log, syslog, web_log, process_events, network_events, file_events) + numeric system stats. The agent learns its own representations.
  • Hybrid text + structured observations (v2) — 3 text channels for logs + 3 fixed-width float32 arrays for eBPF kernel events. Matches how real SOC tooling consumes data: text for human-readable logs, structured arrays for kernel telemetry.
  • Defensive action space — 6 actions (pass / alert / throttle / block_source / unblock / isolate) + continuous risk score. Actions causally affect future observations.
  • Asymmetric rewards — blocking an attacker earns +1.0, blocking a legitimate user costs -1.0. Ongoing consequence feedback from blocked/throttled events accumulates between steps.
  • Continuous streamterminated is always False; the log stream never ends (just like a real server)
  • eBPF kernel events — process execution, network connections, and file access captured via BPF tracepoints. Mirrors how modern EDR agents work.
  • Attack framework — YAML-driven campaign orchestrator with 6 modules: SSH brute force, credential stuffing, Log4Shell, Redis Lua sandbox escape (CVE-2022-0543), port scan, post-auth execution
  • Stream composition — offline mixing of benign + attack data with Poisson-scheduled campaigns and MITRE ATT&CK-weighted type distributions

Observation Space

Text Mode (SecurityLogStream-Text-v0)

The agent sees the same data a security analyst would, raw log files and kernel event streams:

Dict({
    "auth_log":         Text    # SSH auth events (tail of /var/log/auth.log)
    "syslog":           Text    # System events (tail of /var/log/syslog)
    "web_log":          Text    # Combined web access/error logs
    "process_events":   Text    # eBPF: execve/exit kernel events
    "network_events":   Text    # eBPF: connect/accept socket events
    "file_events":      Text    # eBPF: open/unlink file events
    "system_stats":     Box(3)  # [load_avg, mem_used_frac, disk_used_frac]
    "defense_state":    Text    # Optional (defense_state_obs=True): own control state
})

Each text channel is a ring buffer of recent lines (configurable tail_lines and max_chars), updated on every step.

Hybrid Mode (SecurityLogStream-Hybrid-v0)

Log channels remain as text; eBPF kernel events become fixed-width float32 arrays:

Dict({
    "auth_log":         Text              # Unchanged — raw log text
    "syslog":           Text              # Unchanged
    "web_log":          Text              # Unchanged
    "process_events":   Box(50, 8)        # [log_dt, pid, ppid, uid, syscall, comm_hash, parent_hash, tree_depth]
    "network_events":   Box(50, 7)        # [log_dt, pid, uid, syscall, dst_ip_hash, dst_port, comm_hash]
    "file_events":      Box(50, 6)        # [log_dt, pid, uid, syscall, flags, path_hash]
    "system_stats":     Box(3)            # Unchanged
    "defense_state":    Box(6)            # Optional (defense_state_obs=True)
})

Each structured channel is a ring buffer of tail_events rows (default 50). String fields (comm, IP, path) are hashed via mmh3 with per-field seeds. Timestamp deltas are log-scaled (log(1 + dt)) for gradient stability. Process events track tree depth from pid/ppid ancestry.

env = gym.make("SecurityLogStream-Hybrid-v0", db_path="data/exp_7d_brute_v4.db", tail_events=50)
obs, info = env.reset()
print(obs["auth_log"][:100])            # str — raw log text
print(obs["process_events"].shape)      # (50, 8) — float32 array

Defense State (defense_state_obs=True, opt-in)

Without this channel the environment reports only the host's telemetry, so an agent has no observable record of what it has already blocked, throttled, or isolated and must carry that state internally. An operator in the same position would run fail2ban-client status or iptables -L. Enabling defense_state_obs puts that answer in the observation.

Text mode renders it as command-style status output:

=== defense state ===
blocked (2): 10.0.0.5, 192.168.2.77
throttled (1): 198.51.100.2
isolation: off
events_suppressed: 1423
seconds_since_change: 84
current_src: 192.168.2.77 [BLOCKED]

Hybrid mode encodes the same facts as Box(6): [n_blocked, n_throttled, is_isolated, current_src_blocked, current_src_throttled, seconds_since_last_change]. The address list has no fixed-width encoding, so the two membership flags carry the part that bears on the current decision.

The current_src field is what makes de-escalation decidable from the observation rather than from agent memory: it reports whether the source of the event now being observed is already under a control. Every field is a consequence of the agent's own actions, so the channel is ground-truth-blind and leaks no label.

The channel is default-off, which leaves the legacy observation space byte-for-byte unchanged. Whether it becomes the published default is an open decision (see TODO.md).

env = gym.make("SecurityLogStream-Text-v0", db_path="data/exp_7d_brute_v4.db",
               defense_state_obs=True, block_visibility="deny_log")
obs, info = env.reset()
print(obs["defense_state"])

Action Space

Dict({
    "action":     Discrete(6)   # 0=pass, 1=alert, 2=throttle, 3=block_source, 4=unblock, 5=isolate
    "risk_score": Box(0, 10)    # Agent's estimate of current threat level (auxiliary prediction)
    "target":     Discrete(65)  # 0=current event's source, 1..64=target slate slot
})
Action Effect
pass Continue monitoring
alert Flag for human review
throttle Rate-limit source IP (~90% drop)
block_source Add source IP to firewall blocklist (100% drop)
unblock Remove source IP from blocklist/throttle list
isolate Quarantine server (block all network events)

The agent can escalate and de-escalate: throttle -> block -> unblock.

Target selection

target names the address an action applies to. Index 0 is the current event's source, which was the only available binding before 0.6.0; indices 1 to target_slate_size name a slot in the target slate, an observable list of addresses seen anywhere in the stream.

This exists because binding actions to the current event forecloses most of the stream. 93.5% of exp_30d_heavy_v4 events carry no source address at all, and that is structural rather than a parsing gap: 99.9% of address-less events are eBPF, and an openat or execve has no peer address to record. Without target selection an agent that infers compromise from a suspicious execve cannot act on it, because the event it is looking at names no host. With it, the agent blocks the host it saw earlier in auth_log.

The slate is its own observation channel, since an index into a list the agent cannot see is not learnable. Text mode renders it as a numbered list; hybrid mode encodes Box(N, 3) as [addr_hash, is_blocked, is_throttled], hashing addresses with the same seed as the network channel's dst_ip_hash so a slate slot and a kernel event referring to one host hash alike.

=== targets ===
  0 current_src: none
  1 192.168.1.100 [BLOCKED]
  2 10.0.0.5
  3 -

Slots are stable, so a learned index keeps its referent. Eviction is least-recently-seen among addresses not under a control; blocked and throttled addresses are pinned, which is what keeps an already-blocked host nameable for release even under "drop" visibility, where its events stop arriving. If every slot is pinned, a newly seen address does not enter the slate and remains reachable through target 0 while it is the current event.

Two reward rules follow from targeting. An action that changes no state scores 0.0, rather than being paid in full for a block that dropped nothing. An action aimed at a slate address is graded on that address's last observed label rather than on whatever event is on screen, which is what makes it coherent to block a host named from kernel evidence while looking at an unrelated event; the grading uses only labels the agent has already been shown.

Set target_slate_size=0 to restore the pre-0.6.0 action space.

Block recovery (opt-in, default-off)

By default a blocked IP's events are dropped 100% and never reappear within an episode, so an unblock directed at it is unreachable (the IP is no longer observable). Two composable constructor parameters make the block then unblock loop learnable:

  • block_visibility="deny_log" (default "drop"): a blocked IP's events are still dropped by the firewall, but each is surfaced in the observation as a ground-truth-blind "[FIREWALL DENY] " line. The agent can see a wrongly-blocked benign IP still active and unblock it (the deny line makes that IP the current event), or see an attacker go quiet and leave it blocked. The deny rendering never encodes is_malicious, so no label leaks into the observation.
  • block_ttl=<seconds> (default None): fail2ban-style auto-expiry. A block lapses after block_ttl event-seconds; the IP re-surfaces for a fresh decision.
env = gym.make("SecurityLogStream-Text-v0", db_path=..., block_visibility="deny_log", block_ttl=300.0)

Reward Function

Two components by default (an action term and ongoing consequences), plus an optional risk-score term:

Action reward (asymmetric — mistakes in both directions are costly):

Action During Attack During Benign
block_source +1.0 -1.0
throttle +0.75 -0.5
alert +0.5 -0.3
pass -0.5 0.0
isolate +0.25 -2.0
unblock -0.5 0.0

Risk score MSE (optional, disabled by default; enable via reward_config={"include_risk_reward": True}): -0.1 * (predicted_risk - true_risk)^2 — penalizes inaccurate threat assessment. It is off by default because, as a per-event term that accumulates against every observed event, it can make observation-suppressing actions (block/throttle/isolate) rationally dominant.

Ongoing consequences: blocked/throttled events accumulate reward between steps (+0.1 per blocked attack event, -0.5 per blocked benign event). The agent feels the sustained cost of false positives.

Under block_visibility="deny_log", a surfaced deny-log entry contributes only the consequence term: the per-step action and risk terms are zeroed so the event is not double-counted (the firewall, not the agent's live decision, is acting on it). The agent's action on a denied event is graded on the next live event it produces. Episode-total consequence reward is unchanged from "drop" mode; "deny_log" only redistributes it across steps (one step per blocked event) and lengthens the episode.

Supported Attacks

Attack Type Module MITRE Technique MITRE Tactic Description
discovery recon T1046 — Network Service Discovery TA0007 — Discovery SYN port scan via scapy raw sockets
brute_force ssh_brute_force T1110.001 — Password Guessing TA0006 — Credential Access SSH password brute force via paramiko with IP aliasing
web_exploit log4shell T1190 — Exploit Public-Facing Application TA0001 — Initial Access Log4Shell (CVE-2021-44228) JNDI injection via HTTP
credential_stuffing credential_stuffing T1110.004 — Credential Stuffing TA0006 — Credential Access Breach dump credentials, each tried once via SSH
web_exploit redis_lua_escape T1190 — Exploit Public-Facing Application TA0001 — Initial Access Redis Lua sandbox escape (CVE-2022-0543, CVSS 10.0) — 3-stage: enum → Lua sandbox escape via package.loadlib() → post-exploit RCE
execution ssh_post_auth T1059.004 — Unix Shell TA0002 — Execution Post-auth command execution + optional payload download
persistence TA0003 — Persistence Planned
privilege_escalation TA0004 — Privilege Escalation Planned
exfiltration TA0010 — Exfiltration Planned

The first six attacks have implemented modules, campaign configs, and validated datasets. The remaining three tactics are planned and have no module, config, or data.

Campaign Configurations

Nine YAML configs in campaigns/ drive the six modules. Six are single-phase, isolating one module so its signature can be studied on its own; three chain multiple phases with distinct timing profiles per phase.

Config Phases Notes
recon_only.yaml recon SYN scan from spoofed source IPs
ssh_brute_only.yaml ssh_brute_force Aliased IPs, accelerating timing profile
credential_stuffing_only.yaml credential_stuffing Unique pairs tried once each
log4shell_only.yaml log4shell Decelerating profile (spray, then slow to evade)
redis_exploit_only.yaml redis_lua_escape Custom profile: slow enum, fast exploit, slow post-exploit
post_auth_only.yaml ssh_post_auth Valid credentials, system profiling commands
recon_ssh_log4shell.yaml recon -> ssh_brute_force -> log4shell Largest campaign in the dataset
full_killchain.yaml recon -> credential_stuffing -> ssh_post_auth Ends in a successful shell
redis_killchain.yaml recon -> redis_lua_escape -> ssh_post_auth Redis RCE, then SSH pivot

Run one with python -m attacks run campaigns/<config>.yaml (see Running Attack Campaigns). The shipped campaigns_v2.db holds the recorded output of these configs: 60,468 events, of which 30,436 are labeled malicious by time and source-IP matching. The remainder is target-host background and collector activity captured inside the same collection windows, not a benign traffic baseline; the benign baseline comes from benign_v4.db at composition time. Per-campaign counts and known limitations are documented in data/DATASET_README.md.

Redis Lua Sandbox Escape (CVE-2022-0543)

The redis_lua_escape module exploits a Debian-specific vulnerability where Redis is dynamically linked against liblua5.1, allowing package.loadlib() to escape the Lua sandbox for unauthenticated RCE. The attack runs in three stages:

  1. Enumeration — fingerprint Redis via INFO, CONFIG GET *, DBSIZE, CLIENT LIST
  2. Exploitation — Lua sandbox escape via EVAL + package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_io")
  3. Post-exploitation — system commands via repeated EVAL calls (id, whoami, then configurable command profiles)

Key eBPF detection signal: execve events where parent_comm=redis-server — Redis spawning shell commands (sh, bash, id, cat) is highly anomalous. The eBPF collector captures ppid + parent_comm on every process event, so this parent-child relationship appears directly in the process_events text channel.

Baselines

Baseline agents establish performance bounds for the environment. See examples/ for runnable scripts. Results below are from exp_30d_heavy_v4.db (1M steps, seed 42, all 5 attack types), regenerated for 0.6.0.

Agent Description Precision Recall F1 Mean Reward
pass-only Never acts — always passes 0.000 0.000 0.000 -0.0120
random Samples the full action space 0.005 0.675 0.010 -0.4061
threshold(5) Block IP after 5 failed SSH auths in 5 min 1.000 0.005 0.011 +0.0001
keyword Multi-channel SIEM-style pattern matching 0.987 0.013 0.025 -0.0115
rlsecd 5-head MLP continual learner (rlsecd) 0.979 0.979 0.979

Reported under the 0.6.0 defaults: include_risk_reward=False, target_slate_size=64, defense_state_obs=True. The mean-reward column moved substantially from the pre-0.6.0 table for two independent reasons, and the detection columns barely moved at all, since they never depended on the reward.

The larger reason predates this release. The previous table was generated while include_risk_reward still defaulted to True, and was not regenerated when commit a5ceae9 flipped that default off. Its pass-only figure of -0.073 is reproduced exactly by adding the dormant risk-MSE term back (-0.0733 computed directly from the first 1M events), so that column had been describing a reward function the environment no longer computed. The smaller reason is this release: an action that changes no state now scores 0.0 instead of being paid in full, which matters because ~93.5% of steps carry no address for an action to bind to.

Both heuristic agents achieve high precision but near-zero recall. The threshold agent only sees SSH brute force in auth_log text. The keyword agent adds rules for Log4Shell, process ancestry, and file access across all channels, doubling F1 — but still catches only 1.3% of attacks because most malicious events are eBPF kernel telemetry (file opens, process exits, network accepts) that don't match static signatures. Only a learning agent can generalize across the full observation space.

# Run all baselines on an experiment stream
python examples/benchmark.py data/exp_7d_brute_v4.db

# Individual agents
python examples/random_agent.py data/exp_7d_brute_v4.db
python examples/threshold_agent.py data/exp_7d_brute_v4.db --threshold 5
python examples/keyword_agent.py data/exp_7d_brute_v4.db
python examples/streaming_demo.py data/exp_7d_brute_v4.db --mode gym

Install

pip install security-gym

Or from source:

git clone https://github.com/j-klawson/security-gym.git
cd security-gym
pip install -e ".[dev]"

Optional extras:

pip install -e ".[alberta]"   # JAX + alberta-framework for RL experiments
pip install -e ".[attacks]"   # paramiko, requests, scapy for attack generation
pip install -e ".[all]"       # Everything

Dataset

Pre-built datasets (SQLite databases with labeled log and eBPF kernel events) are available from Zenodo and GitHub Releases.

The v4 dataset includes 11.2M benign events (7.9M logs + 3.24M eBPF from 3 servers) and 60K attack events across 5 attack types. Pre-composed experiment streams range from 4.9M events (7-day) to 257.7M events (365-day).

Download the latest dataset:

# Via CLI (after pip install)
security-gym download

# Or list available releases first
security-gym list

Or download from Zenodo and decompress with zstd -d <file>.zst into data/.

Quick Start

Basic Gymnasium Usage

import gymnasium as gym
import numpy as np
import security_gym

env = gym.make("SecurityLogStream-Text-v0", db_path="data/exp_7d_brute_v4.db")
obs, info = env.reset()

# obs is a dict of text channels + system stats
print(obs["auth_log"][:200])   # Raw auth log lines
print(obs["system_stats"])     # [load_avg, mem_used, disk_used]

while True:
    # Choose an action
    action = {
        "action": 0,  # pass (monitor only)
        "risk_score": np.array([0.0], dtype=np.float32),
    }

    obs, reward, terminated, truncated, info = env.step(action)

    # Ground truth (for evaluation, not visible to agent)
    gt = info["ground_truth"]
    print(f"{info['timestamp']} | malicious={gt['is_malicious']} | "
          f"risk={gt['true_risk']:.1f} | reward={reward:.2f}")

    if truncated:  # End of data
        break

Defensive Actions

import numpy as np

# Block the current event's source IP (100% drop)
block = {"action": 3, "risk_score": np.array([8.0], dtype=np.float32)}

# Throttle (90% drop rate)
throttle = {"action": 2, "risk_score": np.array([5.0], dtype=np.float32)}

# Alert with high risk estimate
alert = {"action": 1, "risk_score": np.array([7.0], dtype=np.float32)}

# Undo a block (correct false positive)
unblock = {"action": 4, "risk_score": np.array([1.0], dtype=np.float32)}

# Quarantine server (blocks all network events)
isolate = {"action": 5, "risk_score": np.array([10.0], dtype=np.float32)}

After blocking an IP, future events from that IP are silently dropped. The agent observes the absence of those events and receives ongoing consequence feedback:

  • Dropped attack events: +0.1 per event (confirmed mitigation)
  • Dropped benign events: -0.5 per event (service impact)

ANSI Rendering

env = gym.make("SecurityLogStream-Text-v0", db_path="data/exp_7d_brute_v4.db", render_mode="ansi")
obs, info = env.reset()
for _ in range(20):
    action = {"action": 0, "risk_score": np.array([0.0], dtype=np.float32)}
    obs, reward, terminated, truncated, info = env.step(action)
    print(env.render())  # Color-coded: red=malicious, green=benign

SecurityGymStream (Batch/Streaming Adapter)

For direct integration with learning frameworks (bypasses Gymnasium overhead):

from security_gym.adapters.scan_stream import SecurityGymStream

stream = SecurityGymStream("data/exp_7d_brute_v4.db")

# Batch: load all observations and ground truth
observations, ground_truths = stream.collect_numpy()
# observations: list of dicts (one per event, each with text channels + system_stats)
# ground_truths: list of dicts (is_malicious, attack_type, true_risk, ...)

# Constant-memory streaming
for obs_batch, gt_batch in stream.iter_batches(size=1000):
    for obs, gt in zip(obs_batch, gt_batch):
        print(obs["auth_log"][:80], gt["is_malicious"])

# Server-speed evaluation mode (never-ending, paced stream)
stream = SecurityGymStream("data/exp_7d_brute_v4.db", speed=10.0, loop=True)
for timestep in stream:  # Requires JAX
    ...

Generating Data

Running Attack Campaigns

The attack framework generates labeled data by executing scripted attacks against a target VM and collecting the resulting logs:

# List available attack modules
python -m attacks list-modules

# Validate a campaign config
python -m attacks validate campaigns/ssh_brute_only.yaml

# Dry run (preview without executing)
python -m attacks run campaigns/ssh_brute_only.yaml --dry-run

# Execute (requires network access to target VM)
sudo python -m attacks run campaigns/ssh_brute_only.yaml

Campaign configs are YAML files defining attack phases, timing profiles, IP strategies, and log collection:

campaign:
  name: "SSH Brute Force Only"
  seed: 42
  target:
    host: 192.168.2.201
    ssh_user: researcher
    ssh_key: ~/.ssh/your_public_key
  collection:
    ebpf:
      enabled: true           # Collect kernel events via eBPF
  phases:
    - name: "SSH Brute Force"
      module: ssh_brute_force
      mitre_technique: "T1110.001"
      params:
        usernames: ["root", "admin", "ubuntu"]
        passwords: ["password", "123456", "admin"]
        target_port: 22
        max_attempts_per_ip: 10
      ip_source:
        strategy: aliased
        count: 5
        subnet: "192.168.2.0/24"
      timing:
        duration_seconds: 300
        profile: constant
        jitter_ms: [200, 800]

Importing Benign Logs

Import real server logs as baseline benign data:

python -m attacks import-logs server_logs.tar --db data/benign.db --host myserver

Collecting eBPF Kernel Events

The three kernel observation channels (process_events, network_events, file_events) are populated by an eBPF collector daemon that attaches to Linux kernel tracepoints via BCC. This captures syscall-level activity invisible to traditional log files — the agent sees process execution chains, network connections, and file access as they happen in the kernel.

What's captured:

Channel Tracepoints Fields
process_events sys_enter_execve, sched_process_exit pid, ppid, uid, comm, parent_comm, args, exit code
network_events sys_enter_connect, sys_enter_accept4 pid, uid, comm, dst IP:port
file_events sys_enter_openat, sys_enter_unlinkat pid, comm, path, flags

Process events include parent process ancestry (ppid + parent_comm), allowing the agent to learn causal chains — e.g., apache2 → wget is suspicious while cron → wget may be routine. Network events include the effective UID, so the agent can learn user-identity-aware policies.

Benign baseline collection:

eBPF kernel events are collected from the target server during normal operation (no attacks running) to establish a baseline of benign system activity:

# Collect 1 hour of benign kernel events from the target server
python scripts/collect_ebpf_baseline.py --duration 3600

# Preview without collecting
python scripts/collect_ebpf_baseline.py --duration 3600 --dry-run

This SSHs into the target, deploys the eBPF collector, runs for the specified duration, retrieves the events, and inserts them as benign (is_malicious=0). The v4 benign dataset includes 3.24M eBPF events from 24-hour collections on 3 servers.

During attack campaigns:

When ebpf: {enabled: true} is set in a campaign YAML, the orchestrator automatically starts the eBPF collector before the attack begins and stops it after. Kernel events captured during attack windows are labeled malicious through the same time+IP matching used for log events — an execve wget from the attacker's IP during an attack phase is correctly labeled as part of the attack.

Composing Experiment Streams

Combine benign and attack data into reproducible experiment streams:

# Preview composition plan
python -m attacks compose configs/stream_90d_mixed.yaml --dry-run

# Generate composed stream
python -m attacks compose configs/stream_90d_mixed.yaml

Composition configs control duration, attack frequency, and MITRE ATT&CK-weighted type distributions:

stream:
  duration: 90d
  seed: 42
  benign:
    db: data/benign_v4.db
    ebpf_sample_rate: 0.242    # 24.2%: approximates a single host's event rate
  attacks:
    db: data/campaigns_v2.db
    campaigns_per_day: 3.0
    distribution:
      discovery: 0.35
      brute_force: 0.30
      web_exploit: 0.20
      credential_stuffing: 0.10
      execution: 0.05
  output:
    db: data/exp_90d_v4.db

Project Structure

security-gym/
├── src/security_gym/          # Installable package
│   ├── adapters/              # SecurityGymStream (batch/streaming adapter)
│   ├── data/                  # EventStore (SQLite), StreamComposer
│   ├── envs/                  # SecurityLogStreamEnv (v1, v2), deprecated wrappers
│   ├── features/              # Deprecated (v0 numeric extractors)
│   ├── parsers/               # auth_log, syslog, web_access, web_error, journal, ebpf
│   └── targets/               # Deprecated (v0 multi-head target builder)
├── examples/                  # Baseline agents and usage demos
├── attacks/                   # Attack framework (NOT pip-installed)
│   ├── modules/               # recon, ssh_brute_force, credential_stuffing, ssh_post_auth, log4shell, redis_lua_escape
│   ├── collection/            # SSH/SFTP log collector, benign log importer, eBPF orchestrator
│   ├── labeling/              # Time+IP campaign labeler
│   └── tests/                 # Attack framework tests
├── campaigns/                 # YAML campaign configs
├── configs/                   # YAML composition configs
├── server/                    # Target VM provisioning docs, eBPF collector daemon
└── tests/                     # Core package tests

Development

pip install -e ".[dev]"
pytest tests/                     # Core tests (339 tests)
pytest attacks/tests/             # Attack framework tests (120 tests)
ruff check src/ tests/ attacks/   # Lint

Requirements

  • Python >= 3.11
  • gymnasium >= 1.0.0
  • numpy >= 1.24.0

Author

Keith Lawson

License

Apache-2.0

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

security_gym-0.6.0.tar.gz (485.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

security_gym-0.6.0-py3-none-any.whl (75.2 kB view details)

Uploaded Python 3

File details

Details for the file security_gym-0.6.0.tar.gz.

File metadata

  • Download URL: security_gym-0.6.0.tar.gz
  • Upload date:
  • Size: 485.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for security_gym-0.6.0.tar.gz
Algorithm Hash digest
SHA256 01e195f02f9fae0a22d563e1ff472764bfb7038c656b80968468da28c37ead63
MD5 33b0c72991aed2756cb4f13389e5e01b
BLAKE2b-256 69a61598d22360c72016dc7a460f20c5ce8c9ce8b4e37c1e484a17a3157c3dfb

See more details on using hashes here.

Provenance

The following attestation bundles were made for security_gym-0.6.0.tar.gz:

Publisher: publish.yml on j-klawson/security-gym

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file security_gym-0.6.0-py3-none-any.whl.

File metadata

  • Download URL: security_gym-0.6.0-py3-none-any.whl
  • Upload date:
  • Size: 75.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for security_gym-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4b582295112c8c019af886e6a5968c967853300e257b354b45990ebc06ba3d41
MD5 62a200a898f53ba8df26e416ef80bf23
BLAKE2b-256 b604c825bc20700fecf911dcfe485af559b9c8b650cf5d70028d29de880ba2b2

See more details on using hashes here.

Provenance

The following attestation bundles were made for security_gym-0.6.0-py3-none-any.whl:

Publisher: publish.yml on j-klawson/security-gym

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.6.2

2 files

0.6.1

2 files

This release

0.6.0 This release

2 files

0.5.1

2 files

0.5.0

2 files

0.4.4

2 files

0.4.3

2 files

0.4.2

2 files

0.4.1

2 files

0.4.0

2 files

0.3.12

2 files

0.3.11

2 files

0.3.10

2 files

0.3.9

2 files

0.3.8

2 files

0.3.7

2 files

0.3.6

2 files

0.3.5

2 files

0.3.4

2 files

0.3.3

2 files

0.3.2

2 files

0.3.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page