aisoc-sandbox 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aisoc_sandbox-0.1.0/PKG-INFO +154 -0
- aisoc_sandbox-0.1.0/README.md +127 -0
- aisoc_sandbox-0.1.0/pyproject.toml +66 -0
- aisoc_sandbox-0.1.0/setup.cfg +4 -0
- aisoc_sandbox-0.1.0/src/aisoc_sandbox/__init__.py +42 -0
- aisoc_sandbox-0.1.0/src/aisoc_sandbox/cli.py +150 -0
- aisoc_sandbox-0.1.0/src/aisoc_sandbox/investigation.py +307 -0
- aisoc_sandbox-0.1.0/src/aisoc_sandbox/ledger.py +154 -0
- aisoc_sandbox-0.1.0/src/aisoc_sandbox/py.typed +0 -0
- aisoc_sandbox-0.1.0/src/aisoc_sandbox/scenarios/aws-credential-exfil.json +48 -0
- aisoc_sandbox-0.1.0/src/aisoc_sandbox/scenarios/github-token-theft.json +58 -0
- aisoc_sandbox-0.1.0/src/aisoc_sandbox/scenarios/kubernetes-privesc.json +51 -0
- aisoc_sandbox-0.1.0/src/aisoc_sandbox/scenarios/lateral-movement.json +37 -0
- aisoc_sandbox-0.1.0/src/aisoc_sandbox/scenarios/phishing-payload.json +46 -0
- aisoc_sandbox-0.1.0/src/aisoc_sandbox/scenarios.py +126 -0
- aisoc_sandbox-0.1.0/src/aisoc_sandbox.egg-info/PKG-INFO +154 -0
- aisoc_sandbox-0.1.0/src/aisoc_sandbox.egg-info/SOURCES.txt +21 -0
- aisoc_sandbox-0.1.0/src/aisoc_sandbox.egg-info/dependency_links.txt +1 -0
- aisoc_sandbox-0.1.0/src/aisoc_sandbox.egg-info/entry_points.txt +2 -0
- aisoc_sandbox-0.1.0/src/aisoc_sandbox.egg-info/requires.txt +4 -0
- aisoc_sandbox-0.1.0/src/aisoc_sandbox.egg-info/top_level.txt +1 -0
- aisoc_sandbox-0.1.0/tests/test_determinism.py +124 -0
- aisoc_sandbox-0.1.0/tests/test_smoke.py +173 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aisoc-sandbox
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Run an AiSOC agent investigation offline in under 30 seconds. No Docker, no API key.
|
|
5
|
+
Author: AiSOC contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/beenuar/AiSOC
|
|
8
|
+
Project-URL: Documentation, https://beenuar.github.io/AiSOC/
|
|
9
|
+
Project-URL: Source, https://github.com/beenuar/AiSOC/tree/main/packages/aisoc-sandbox
|
|
10
|
+
Project-URL: Issues, https://github.com/beenuar/AiSOC/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/beenuar/AiSOC/blob/main/CHANGELOG.md
|
|
12
|
+
Keywords: aisoc,soc,security,agent,investigation,ledger,offline
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Information Technology
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Security
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
26
|
+
Requires-Dist: pytest-cov>=4.1; extra == "dev"
|
|
27
|
+
|
|
28
|
+
# aisoc-sandbox
|
|
29
|
+
|
|
30
|
+
> Run an AiSOC agent investigation **offline in under 30 seconds**. No Docker, no API key, no network.
|
|
31
|
+
|
|
32
|
+
[](https://github.com/beenuar/AiSOC/blob/main/LICENSE)
|
|
33
|
+
[](https://github.com/beenuar/AiSOC/blob/main/CHANGELOG.md)
|
|
34
|
+
|
|
35
|
+
`aisoc-sandbox` is the quickest possible on-ramp to [AiSOC](https://github.com/beenuar/AiSOC). It walks one alert fixture through a four-stage agent funnel — **Detect → Triage → Hunt → Respond** — using a deterministic offline reasoner in place of a real LLM, and prints the resulting Investigation Ledger to your terminal.
|
|
36
|
+
|
|
37
|
+
It is the simulator-equivalent of the production [`services/agents/`](https://github.com/beenuar/AiSOC/tree/main/services/agents) graph, collapsed into a single zero-dependency Python package. **When you're ready to run the real stack: `pnpm aisoc:demo` from a fresh clone of [AiSOC](https://github.com/beenuar/AiSOC).**
|
|
38
|
+
|
|
39
|
+
## Why this exists
|
|
40
|
+
|
|
41
|
+
The production AiSOC stack needs Postgres, Kafka, Redis, an LLM API key, and ~5 minutes to boot. That's the right cost for a buyer evaluating against their own alert data — but it's the wrong cost for a developer who just wants to see how the agent reasons before they commit their evening.
|
|
42
|
+
|
|
43
|
+
This package collapses the boot time to **< 5 seconds** and the disk footprint to **< 50 KB**. The trade-off is that the reasoning is deterministic and the tools are simulated (not executed); see "Differences from the production stack" below.
|
|
44
|
+
|
|
45
|
+
## Install
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Today (from this monorepo):
|
|
49
|
+
git clone https://github.com/beenuar/AiSOC.git
|
|
50
|
+
cd AiSOC && pip install -e packages/aisoc-sandbox
|
|
51
|
+
|
|
52
|
+
# Once published to PyPI (ready, unpublished — the upload is blocked on
|
|
53
|
+
# registry credentials, which is an account action, not a code change):
|
|
54
|
+
# pip install aisoc-sandbox
|
|
55
|
+
# pipx run aisoc-sandbox demo
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Python 3.10+ on Linux / macOS / Windows. Zero runtime dependencies.
|
|
59
|
+
|
|
60
|
+
## Quick start
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# Walk the default scenario (lateral-movement) through the funnel
|
|
64
|
+
aisoc-sandbox demo
|
|
65
|
+
|
|
66
|
+
# Pick a different bundled scenario
|
|
67
|
+
aisoc-sandbox demo --scenario aws-credential-exfil
|
|
68
|
+
|
|
69
|
+
# Use your own scenario JSON
|
|
70
|
+
aisoc-sandbox demo --file ./my-alert.json
|
|
71
|
+
|
|
72
|
+
# Machine-readable output
|
|
73
|
+
aisoc-sandbox demo --scenario phishing-payload --json | jq
|
|
74
|
+
|
|
75
|
+
# What scenarios are bundled?
|
|
76
|
+
aisoc-sandbox scenarios
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Bundled scenarios
|
|
80
|
+
|
|
81
|
+
Five scenarios ship with the package; each one is a single JSON file under [`src/aisoc_sandbox/scenarios/`](./src/aisoc_sandbox/scenarios) and is small enough to read end-to-end:
|
|
82
|
+
|
|
83
|
+
| ID | Title | MITRE | Severity |
|
|
84
|
+
|---|---|---|---|
|
|
85
|
+
| `lateral-movement` | Impossible-travel Okta sign-in | T1078, T1078.004 | high |
|
|
86
|
+
| `aws-credential-exfil` | IAM keys used from new ASN, then `s3:GetObject` flood | T1552, T1567, T1078.004 | critical |
|
|
87
|
+
| `phishing-payload` | Click-through to credential-harvest page | T1566, T1566.002 | high |
|
|
88
|
+
| `kubernetes-privesc` | Namespace SA bound to `cluster-admin` | T1098, T1078 | critical |
|
|
89
|
+
| `github-token-theft` | PAT leaked, six private repos cloned in 11 s | T1078, T1555, T1567 | high |
|
|
90
|
+
|
|
91
|
+
## What you'll see
|
|
92
|
+
|
|
93
|
+
Each `aisoc-sandbox demo` run emits a four-step ledger:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
Investigation Ledger
|
|
97
|
+
4 steps · 12 ms total · synthetic offline run (no LLM, no Docker)
|
|
98
|
+
|
|
99
|
+
Step 0 DETECT DetectAgent (3 ms)
|
|
100
|
+
Action Match incoming events against detection ruleset
|
|
101
|
+
Rationale 2 event(s) ingested; matched detection ruleset against MITRE techniques T1078, T1078.004.
|
|
102
|
+
· events_ingested: 2
|
|
103
|
+
· mitre_techniques: ['T1078', 'T1078.004']
|
|
104
|
+
· severity_at_intake: high
|
|
105
|
+
· entity:user: alice@example.com
|
|
106
|
+
→ would-call rules.match({"rule_count": "800+", ...})
|
|
107
|
+
→ would-call fusion.score({"window_minutes": 15})
|
|
108
|
+
Decision Open alert at severity=high
|
|
109
|
+
|
|
110
|
+
Step 1 TRIAGE TriageAgent (3 ms)
|
|
111
|
+
Action Score alert confidence + cross-reference with prior cases
|
|
112
|
+
Rationale Authenticated session signals look legitimate at the protocol layer, but the geo pivot between sequential events is physically impossible — classic credential takeover.
|
|
113
|
+
...
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The shape mirrors the production Investigation Rail at [`/alerts/[id]`](https://github.com/beenuar/AiSOC/blob/main/apps/docs/docs/console/investigation-rail.md). The four stages, the evidence chips, and the "Decision" lines are the same — only the LLM rationale and tool execution are simulated.
|
|
117
|
+
|
|
118
|
+
## Library use
|
|
119
|
+
|
|
120
|
+
The package's surface is small enough to embed:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from aisoc_sandbox import load_scenario, run_investigation
|
|
124
|
+
|
|
125
|
+
scenario = load_scenario("aws-credential-exfil")
|
|
126
|
+
ledger = run_investigation(scenario)
|
|
127
|
+
|
|
128
|
+
# Iterate the steps
|
|
129
|
+
for step in ledger:
|
|
130
|
+
print(step.step, step.agent, step.action, step.decision)
|
|
131
|
+
|
|
132
|
+
# Or render to a stream (TTY-aware colour)
|
|
133
|
+
ledger.render_human()
|
|
134
|
+
|
|
135
|
+
# Or serialise
|
|
136
|
+
print(ledger.to_json())
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Differences from the production stack
|
|
140
|
+
|
|
141
|
+
| | `aisoc-sandbox` | Production `services/agents/` |
|
|
142
|
+
|---|---|---|
|
|
143
|
+
| LLM | Deterministic template-driven stub | OpenAI / Anthropic / Ollama via LiteLLM |
|
|
144
|
+
| Tool calls | Simulated as "would-call(name, args)" | Dispatched to connector / action services |
|
|
145
|
+
| Persistence | In-memory; one CLI invocation | Postgres `investigation_events` table |
|
|
146
|
+
| Latency | Synthetic per-stage numbers | Real LLM + tool latency |
|
|
147
|
+
| Boot time | < 5 s | ~5 min cold, ~3.5 min warm |
|
|
148
|
+
| Dependencies | None | Postgres + Kafka + Redis + LLM API key |
|
|
149
|
+
|
|
150
|
+
This is on purpose. The sandbox is the **on-ramp**, not a replacement: it gives you 30-second visibility into how the funnel hangs together so you can decide whether the full demo is worth the 5-minute boot.
|
|
151
|
+
|
|
152
|
+
## License
|
|
153
|
+
|
|
154
|
+
MIT — see the [repo LICENSE](https://github.com/beenuar/AiSOC/blob/main/LICENSE).
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# aisoc-sandbox
|
|
2
|
+
|
|
3
|
+
> Run an AiSOC agent investigation **offline in under 30 seconds**. No Docker, no API key, no network.
|
|
4
|
+
|
|
5
|
+
[](https://github.com/beenuar/AiSOC/blob/main/LICENSE)
|
|
6
|
+
[](https://github.com/beenuar/AiSOC/blob/main/CHANGELOG.md)
|
|
7
|
+
|
|
8
|
+
`aisoc-sandbox` is the quickest possible on-ramp to [AiSOC](https://github.com/beenuar/AiSOC). It walks one alert fixture through a four-stage agent funnel — **Detect → Triage → Hunt → Respond** — using a deterministic offline reasoner in place of a real LLM, and prints the resulting Investigation Ledger to your terminal.
|
|
9
|
+
|
|
10
|
+
It is the simulator-equivalent of the production [`services/agents/`](https://github.com/beenuar/AiSOC/tree/main/services/agents) graph, collapsed into a single zero-dependency Python package. **When you're ready to run the real stack: `pnpm aisoc:demo` from a fresh clone of [AiSOC](https://github.com/beenuar/AiSOC).**
|
|
11
|
+
|
|
12
|
+
## Why this exists
|
|
13
|
+
|
|
14
|
+
The production AiSOC stack needs Postgres, Kafka, Redis, an LLM API key, and ~5 minutes to boot. That's the right cost for a buyer evaluating against their own alert data — but it's the wrong cost for a developer who just wants to see how the agent reasons before they commit their evening.
|
|
15
|
+
|
|
16
|
+
This package collapses the boot time to **< 5 seconds** and the disk footprint to **< 50 KB**. The trade-off is that the reasoning is deterministic and the tools are simulated (not executed); see "Differences from the production stack" below.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Today (from this monorepo):
|
|
22
|
+
git clone https://github.com/beenuar/AiSOC.git
|
|
23
|
+
cd AiSOC && pip install -e packages/aisoc-sandbox
|
|
24
|
+
|
|
25
|
+
# Once published to PyPI (ready, unpublished — the upload is blocked on
|
|
26
|
+
# registry credentials, which is an account action, not a code change):
|
|
27
|
+
# pip install aisoc-sandbox
|
|
28
|
+
# pipx run aisoc-sandbox demo
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Python 3.10+ on Linux / macOS / Windows. Zero runtime dependencies.
|
|
32
|
+
|
|
33
|
+
## Quick start
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Walk the default scenario (lateral-movement) through the funnel
|
|
37
|
+
aisoc-sandbox demo
|
|
38
|
+
|
|
39
|
+
# Pick a different bundled scenario
|
|
40
|
+
aisoc-sandbox demo --scenario aws-credential-exfil
|
|
41
|
+
|
|
42
|
+
# Use your own scenario JSON
|
|
43
|
+
aisoc-sandbox demo --file ./my-alert.json
|
|
44
|
+
|
|
45
|
+
# Machine-readable output
|
|
46
|
+
aisoc-sandbox demo --scenario phishing-payload --json | jq
|
|
47
|
+
|
|
48
|
+
# What scenarios are bundled?
|
|
49
|
+
aisoc-sandbox scenarios
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Bundled scenarios
|
|
53
|
+
|
|
54
|
+
Five scenarios ship with the package; each one is a single JSON file under [`src/aisoc_sandbox/scenarios/`](./src/aisoc_sandbox/scenarios) and is small enough to read end-to-end:
|
|
55
|
+
|
|
56
|
+
| ID | Title | MITRE | Severity |
|
|
57
|
+
|---|---|---|---|
|
|
58
|
+
| `lateral-movement` | Impossible-travel Okta sign-in | T1078, T1078.004 | high |
|
|
59
|
+
| `aws-credential-exfil` | IAM keys used from new ASN, then `s3:GetObject` flood | T1552, T1567, T1078.004 | critical |
|
|
60
|
+
| `phishing-payload` | Click-through to credential-harvest page | T1566, T1566.002 | high |
|
|
61
|
+
| `kubernetes-privesc` | Namespace SA bound to `cluster-admin` | T1098, T1078 | critical |
|
|
62
|
+
| `github-token-theft` | PAT leaked, six private repos cloned in 11 s | T1078, T1555, T1567 | high |
|
|
63
|
+
|
|
64
|
+
## What you'll see
|
|
65
|
+
|
|
66
|
+
Each `aisoc-sandbox demo` run emits a four-step ledger:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
Investigation Ledger
|
|
70
|
+
4 steps · 12 ms total · synthetic offline run (no LLM, no Docker)
|
|
71
|
+
|
|
72
|
+
Step 0 DETECT DetectAgent (3 ms)
|
|
73
|
+
Action Match incoming events against detection ruleset
|
|
74
|
+
Rationale 2 event(s) ingested; matched detection ruleset against MITRE techniques T1078, T1078.004.
|
|
75
|
+
· events_ingested: 2
|
|
76
|
+
· mitre_techniques: ['T1078', 'T1078.004']
|
|
77
|
+
· severity_at_intake: high
|
|
78
|
+
· entity:user: alice@example.com
|
|
79
|
+
→ would-call rules.match({"rule_count": "800+", ...})
|
|
80
|
+
→ would-call fusion.score({"window_minutes": 15})
|
|
81
|
+
Decision Open alert at severity=high
|
|
82
|
+
|
|
83
|
+
Step 1 TRIAGE TriageAgent (3 ms)
|
|
84
|
+
Action Score alert confidence + cross-reference with prior cases
|
|
85
|
+
Rationale Authenticated session signals look legitimate at the protocol layer, but the geo pivot between sequential events is physically impossible — classic credential takeover.
|
|
86
|
+
...
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The shape mirrors the production Investigation Rail at [`/alerts/[id]`](https://github.com/beenuar/AiSOC/blob/main/apps/docs/docs/console/investigation-rail.md). The four stages, the evidence chips, and the "Decision" lines are the same — only the LLM rationale and tool execution are simulated.
|
|
90
|
+
|
|
91
|
+
## Library use
|
|
92
|
+
|
|
93
|
+
The package's surface is small enough to embed:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from aisoc_sandbox import load_scenario, run_investigation
|
|
97
|
+
|
|
98
|
+
scenario = load_scenario("aws-credential-exfil")
|
|
99
|
+
ledger = run_investigation(scenario)
|
|
100
|
+
|
|
101
|
+
# Iterate the steps
|
|
102
|
+
for step in ledger:
|
|
103
|
+
print(step.step, step.agent, step.action, step.decision)
|
|
104
|
+
|
|
105
|
+
# Or render to a stream (TTY-aware colour)
|
|
106
|
+
ledger.render_human()
|
|
107
|
+
|
|
108
|
+
# Or serialise
|
|
109
|
+
print(ledger.to_json())
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Differences from the production stack
|
|
113
|
+
|
|
114
|
+
| | `aisoc-sandbox` | Production `services/agents/` |
|
|
115
|
+
|---|---|---|
|
|
116
|
+
| LLM | Deterministic template-driven stub | OpenAI / Anthropic / Ollama via LiteLLM |
|
|
117
|
+
| Tool calls | Simulated as "would-call(name, args)" | Dispatched to connector / action services |
|
|
118
|
+
| Persistence | In-memory; one CLI invocation | Postgres `investigation_events` table |
|
|
119
|
+
| Latency | Synthetic per-stage numbers | Real LLM + tool latency |
|
|
120
|
+
| Boot time | < 5 s | ~5 min cold, ~3.5 min warm |
|
|
121
|
+
| Dependencies | None | Postgres + Kafka + Redis + LLM API key |
|
|
122
|
+
|
|
123
|
+
This is on purpose. The sandbox is the **on-ramp**, not a replacement: it gives you 30-second visibility into how the funnel hangs together so you can decide whether the full demo is worth the 5-minute boot.
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
MIT — see the [repo LICENSE](https://github.com/beenuar/AiSOC/blob/main/LICENSE).
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "aisoc-sandbox"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Run an AiSOC agent investigation offline in under 30 seconds. No Docker, no API key."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "AiSOC contributors" }]
|
|
13
|
+
keywords = ["aisoc", "soc", "security", "agent", "investigation", "ledger", "offline"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Information Technology",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Topic :: Security",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
# Zero runtime dependencies on purpose. The whole point of the sandbox
|
|
27
|
+
# is that `pip install aisoc-sandbox && aisoc-sandbox demo` works on a
|
|
28
|
+
# clean machine inside 30 seconds.
|
|
29
|
+
dependencies = []
|
|
30
|
+
|
|
31
|
+
[project.optional-dependencies]
|
|
32
|
+
dev = [
|
|
33
|
+
"pytest>=8.0",
|
|
34
|
+
"pytest-cov>=4.1",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://github.com/beenuar/AiSOC"
|
|
39
|
+
Documentation = "https://beenuar.github.io/AiSOC/"
|
|
40
|
+
Source = "https://github.com/beenuar/AiSOC/tree/main/packages/aisoc-sandbox"
|
|
41
|
+
Issues = "https://github.com/beenuar/AiSOC/issues"
|
|
42
|
+
Changelog = "https://github.com/beenuar/AiSOC/blob/main/CHANGELOG.md"
|
|
43
|
+
|
|
44
|
+
[project.scripts]
|
|
45
|
+
aisoc-sandbox = "aisoc_sandbox.cli:main"
|
|
46
|
+
|
|
47
|
+
[tool.setuptools]
|
|
48
|
+
package-dir = { "" = "src" }
|
|
49
|
+
|
|
50
|
+
[tool.setuptools.packages.find]
|
|
51
|
+
where = ["src"]
|
|
52
|
+
|
|
53
|
+
[tool.setuptools.package-data]
|
|
54
|
+
aisoc_sandbox = ["scenarios/*.json", "py.typed"]
|
|
55
|
+
|
|
56
|
+
[tool.pytest.ini_options]
|
|
57
|
+
addopts = "-ra --strict-markers"
|
|
58
|
+
testpaths = ["tests"]
|
|
59
|
+
|
|
60
|
+
# Same shape as packages/sdk-py, plugin-sdk-py and aisoc-cli — the three
|
|
61
|
+
# package trees that already declare one. `python_version` is pinned to 3.11
|
|
62
|
+
# even though the wheel supports 3.10+: the baseline has to be reproducible,
|
|
63
|
+
# and 3.11 is the interpreter CI runs.
|
|
64
|
+
[tool.mypy]
|
|
65
|
+
strict = true
|
|
66
|
+
python_version = "3.11"
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""aisoc-sandbox: run an AiSOC agent investigation offline in under 30 seconds.
|
|
2
|
+
|
|
3
|
+
This package is the quickest possible on-ramp to AiSOC: no Docker, no
|
|
4
|
+
Postgres / Kafka / Redis, no API key, no network — `aisoc-sandbox demo`
|
|
5
|
+
walks one alert fixture through a Detect → Triage → Hunt → Respond
|
|
6
|
+
agent funnel and prints a step-by-step Investigation Ledger to stdout.
|
|
7
|
+
|
|
8
|
+
It is a deterministic, in-memory simulator of the production stack at
|
|
9
|
+
[`services/agents/`](https://github.com/beenuar/AiSOC/tree/main/services/agents).
|
|
10
|
+
The shape of the ledger, the funnel stages, the decision metadata, and
|
|
11
|
+
the recommended actions all mirror what the real four-agent system in
|
|
12
|
+
the monorepo emits. The simulator is intentionally NOT the production
|
|
13
|
+
graph: the production graph requires Postgres + Kafka + an LLM API
|
|
14
|
+
key, and a 30-second offline demo cannot afford any of those.
|
|
15
|
+
|
|
16
|
+
When you're ready to run the real stack: `pnpm aisoc:demo`.
|
|
17
|
+
|
|
18
|
+
Public entry points:
|
|
19
|
+
|
|
20
|
+
- :class:`Investigation` — one investigation run.
|
|
21
|
+
- :class:`Ledger` — the step-by-step record.
|
|
22
|
+
- :func:`load_scenario` — load a built-in or user-supplied scenario.
|
|
23
|
+
- :func:`run_investigation` — high-level orchestrator (used by the CLI).
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
from .investigation import Investigation, run_investigation
|
|
29
|
+
from .ledger import Ledger, LedgerStep
|
|
30
|
+
from .scenarios import Scenario, available_scenarios, load_scenario
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"Investigation",
|
|
34
|
+
"Ledger",
|
|
35
|
+
"LedgerStep",
|
|
36
|
+
"Scenario",
|
|
37
|
+
"available_scenarios",
|
|
38
|
+
"load_scenario",
|
|
39
|
+
"run_investigation",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""aisoc-sandbox — command-line entry point.
|
|
2
|
+
|
|
3
|
+
The two visible subcommands are ``demo`` and ``scenarios``:
|
|
4
|
+
|
|
5
|
+
* ``aisoc-sandbox demo`` runs one full Detect → Triage → Hunt →
|
|
6
|
+
Respond funnel against a bundled or user-supplied scenario and
|
|
7
|
+
prints the Investigation Ledger to stdout. ``--json`` switches
|
|
8
|
+
the output to a machine-readable form.
|
|
9
|
+
|
|
10
|
+
* ``aisoc-sandbox scenarios`` lists the bundled scenarios so a user
|
|
11
|
+
can pick one with ``--scenario <id>`` without reading the docs.
|
|
12
|
+
|
|
13
|
+
Exit codes follow UNIX convention:
|
|
14
|
+
|
|
15
|
+
0 success — investigation ran and the ledger was emitted.
|
|
16
|
+
2 invalid arguments (bad scenario id, missing file, etc).
|
|
17
|
+
3 internal error during the run.
|
|
18
|
+
|
|
19
|
+
There is no networked path in this CLI. If you `strace` it you should
|
|
20
|
+
see exactly one ``read`` of the scenario file, zero ``connect`` syscalls.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import argparse
|
|
26
|
+
import json
|
|
27
|
+
import sys
|
|
28
|
+
import time
|
|
29
|
+
from typing import Sequence
|
|
30
|
+
|
|
31
|
+
from . import __version__
|
|
32
|
+
from .investigation import run_investigation
|
|
33
|
+
from .ledger import Ledger
|
|
34
|
+
from .scenarios import available_scenarios, emit_scenario_index, load_scenario
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
_PROG = "aisoc-sandbox"
|
|
38
|
+
_DESCRIPTION = "Run an AiSOC agent investigation offline in under 30 seconds. No Docker, no API key, no network."
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
42
|
+
p = argparse.ArgumentParser(
|
|
43
|
+
prog=_PROG,
|
|
44
|
+
description=_DESCRIPTION,
|
|
45
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
46
|
+
epilog=(
|
|
47
|
+
"Examples:\n"
|
|
48
|
+
" aisoc-sandbox demo\n"
|
|
49
|
+
" aisoc-sandbox demo --scenario aws-credential-exfil\n"
|
|
50
|
+
" aisoc-sandbox demo --file my-alert.json --json\n"
|
|
51
|
+
" aisoc-sandbox scenarios\n"
|
|
52
|
+
),
|
|
53
|
+
)
|
|
54
|
+
p.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
|
|
55
|
+
sub = p.add_subparsers(dest="command", required=True, metavar="<command>")
|
|
56
|
+
|
|
57
|
+
demo = sub.add_parser(
|
|
58
|
+
"demo",
|
|
59
|
+
help="Walk one scenario through the Detect → Triage → Hunt → Respond funnel.",
|
|
60
|
+
)
|
|
61
|
+
demo.add_argument(
|
|
62
|
+
"--scenario",
|
|
63
|
+
choices=available_scenarios(),
|
|
64
|
+
default="lateral-movement",
|
|
65
|
+
help="Bundled scenario id (default: %(default)s).",
|
|
66
|
+
)
|
|
67
|
+
demo.add_argument(
|
|
68
|
+
"--file",
|
|
69
|
+
metavar="PATH",
|
|
70
|
+
help="Path to a custom scenario JSON. Overrides --scenario when set.",
|
|
71
|
+
)
|
|
72
|
+
demo.add_argument(
|
|
73
|
+
"--json",
|
|
74
|
+
action="store_true",
|
|
75
|
+
help="Emit the ledger as a JSON document instead of the human view.",
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
sub.add_parser(
|
|
79
|
+
"scenarios",
|
|
80
|
+
help="List the bundled scenarios and exit.",
|
|
81
|
+
)
|
|
82
|
+
return p
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
86
|
+
parser = _build_parser()
|
|
87
|
+
args = parser.parse_args(argv)
|
|
88
|
+
|
|
89
|
+
if args.command == "scenarios":
|
|
90
|
+
emit_scenario_index()
|
|
91
|
+
return 0
|
|
92
|
+
|
|
93
|
+
if args.command == "demo":
|
|
94
|
+
try:
|
|
95
|
+
scenario = load_scenario(args.scenario, file=args.file)
|
|
96
|
+
except (FileNotFoundError, ValueError) as exc:
|
|
97
|
+
print(f"{_PROG}: error: {exc}", file=sys.stderr)
|
|
98
|
+
return 2
|
|
99
|
+
|
|
100
|
+
ledger = Ledger()
|
|
101
|
+
started = time.perf_counter()
|
|
102
|
+
try:
|
|
103
|
+
run_investigation(scenario, ledger=ledger)
|
|
104
|
+
except Exception as exc: # noqa: BLE001 — surface to the user, exit 3.
|
|
105
|
+
print(f"{_PROG}: investigation crashed: {exc}", file=sys.stderr)
|
|
106
|
+
return 3
|
|
107
|
+
elapsed_ms = int((time.perf_counter() - started) * 1000)
|
|
108
|
+
|
|
109
|
+
if args.json:
|
|
110
|
+
payload = {
|
|
111
|
+
"tool": _PROG,
|
|
112
|
+
"version": __version__,
|
|
113
|
+
"scenario": scenario.to_dict(),
|
|
114
|
+
"ledger": ledger.to_dict(),
|
|
115
|
+
"elapsed_ms": elapsed_ms,
|
|
116
|
+
}
|
|
117
|
+
print(json.dumps(payload, indent=2, ensure_ascii=False))
|
|
118
|
+
return 0
|
|
119
|
+
|
|
120
|
+
# Human view: brief preamble, then the rendered ledger.
|
|
121
|
+
_print_preamble(scenario)
|
|
122
|
+
ledger.render_human()
|
|
123
|
+
print(
|
|
124
|
+
f"Ran {len(ledger)} steps in {elapsed_ms} ms.\n"
|
|
125
|
+
"Ready for the real stack? `pnpm aisoc:demo` from a fresh clone of\n"
|
|
126
|
+
" https://github.com/beenuar/AiSOC\n"
|
|
127
|
+
)
|
|
128
|
+
return 0
|
|
129
|
+
|
|
130
|
+
# argparse refused to leave us here, but Pylance doesn't know that.
|
|
131
|
+
parser.print_help()
|
|
132
|
+
return 2
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _print_preamble(scenario: object) -> None:
|
|
136
|
+
sc = scenario # narrow for the type-checker
|
|
137
|
+
# We don't depend on rich so the package stays zero-dep. The
|
|
138
|
+
# output uses plain text + ANSI; piping to a file produces clean
|
|
139
|
+
# text via Ledger.render_human's TTY-aware colour code.
|
|
140
|
+
print(f"\nScenario: {sc.id}")
|
|
141
|
+
print(f"Title: {sc.title}")
|
|
142
|
+
if sc.narrative:
|
|
143
|
+
print(f"Narrative: {sc.narrative}")
|
|
144
|
+
if sc.mitre_techniques:
|
|
145
|
+
print(f"MITRE: {', '.join(sc.mitre_techniques)}")
|
|
146
|
+
print(f"Severity: {sc.severity}\n")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
if __name__ == "__main__": # pragma: no cover — covered by smoke test.
|
|
150
|
+
sys.exit(main())
|