WHOAM-EYE commited on
Commit
38e5c55
Β·
verified Β·
1 Parent(s): 8dd2117

Upload folder using huggingface_hub

Browse files
Files changed (2) hide show
  1. README.md +352 -324
  2. inference.py +3 -5
README.md CHANGED
@@ -1,4 +1,3 @@
1
-
2
  ---
3
  title: Network Forensics Environment
4
  emoji: "πŸ›°οΈ"
@@ -8,331 +7,360 @@ sdk: docker
8
  sdk_version: "1.0.0"
9
  pinned: false
10
  app_port: 8000
11
- base_path: /web
12
  tags:
13
  - openenv
 
 
14
  ---
15
 
16
  # Network Forensics Environment
17
-
18
- `network_forensics` is an OpenEnv benchmark for packet triage and intrusion investigation. It simulates a real analyst workflow: inspect traffic, flag malicious packets, group related activity into sessions, classify attack patterns, identify the likely entry point, and finish with a final report.
19
-
20
- This is not a toy environment. Episodes are backed by generated PCAP traces and deterministic JSON answer keys, so agents can be evaluated consistently across runs.
21
-
22
- ## Why This Environment Exists
23
-
24
- Security analysts regularly work through packet captures and network telemetry to answer questions such as:
25
-
26
- - Which packets are suspicious?
27
- - Which packets belong to the same attack session?
28
- - What kind of attack is occurring?
29
- - Where did the intrusion begin?
30
-
31
- This environment turns that process into a reproducible benchmark for LLM and RL agents.
32
-
33
- ## Core Workflow
34
-
35
- At each episode, the agent receives a visible packet window and must make investigation decisions step by step.
36
-
37
- Typical flow:
38
-
39
- 1. inspect packets to reveal payloads
40
- 2. flag suspicious packets
41
- 3. group related packets into sessions
42
- 4. tag sessions with attack types
43
- 5. identify the likely entry point
44
- 6. submit a report
45
-
46
- Ground truth is stored in per-task JSON files under `pcaps/`, and grading is deterministic.
47
-
48
- ## Tasks
49
-
50
- The benchmark ships with three tasks of increasing difficulty.
51
-
52
- ### Easy
53
-
54
- - Dataset flavor: DDoS-heavy traffic mixed with benign flows
55
- - Files: `pcaps/easy_task.pcap`, `pcaps/easy_task.json`
56
- - Goal: find the dominant attack traffic and recover the main malicious session
57
-
58
- ### Medium
59
-
60
- - Dataset flavor: web attack traffic
61
- - Attack families: `web_bruteforce`, `web_xss`, `web_sql_injection`
62
- - Files: `pcaps/medium_task.pcap`, `pcaps/medium_task.json`
63
- - Goal: distinguish multiple web attack behaviors and group them correctly
64
-
65
- ### Hard
66
-
67
- - Dataset flavor: high-noise denial-of-service traffic
68
- - Attack families: `dos_hulk`, `dos_goldeneye`, `dos_slowloris`, `dos_slowhttptest`, `heartbleed`
69
- - Files: `pcaps/hard_task.pcap`, `pcaps/hard_task.json`
70
- - Goal: recover multiple malicious sessions in noisy traffic and avoid incorrect tagging
71
-
72
- ## Action Space
73
-
74
- The environment uses the `NetworkForensicsAction` model:
75
-
76
- ```python
77
- class NetworkForensicsAction(Action):
78
- action_type: str
79
- packet_id: Optional[str] = None
80
- packet_ids: Optional[List[str]] = None
81
- session_name: Optional[str] = None
82
- pattern_type: Optional[str] = None
83
- claimed_entry_point: Optional[str] = None
84
- ```
85
-
86
- Supported actions:
87
-
88
- - `inspect_packet`
89
- Reveal the payload of `packet_id`.
90
- - `flag_as_suspicious`
91
- Mark `packet_id` as suspicious.
92
- - `group_into_session`
93
- Group `packet_ids` into `session_name`.
94
- - `tag_pattern`
95
- Assign an attack label to a session.
96
- - `identify_entry_point`
97
- Claim the first malicious packet.
98
- - `submit_report`
99
- End the episode and trigger final report scoring.
100
-
101
- ## Observation Space
102
-
103
- The environment returns `NetworkForensicsObservation`:
104
-
105
- ```python
106
- class NetworkForensicsObservation(Observation):
107
- step_number: int
108
- steps_remaining: int
109
- total_packets: int
110
- visible_packets: List[PacketRecord]
111
- flagged_packet_ids: List[str]
112
- grouped_sessions: Dict[str, List[str]]
113
- tagged_patterns: Dict[str, str]
114
- claimed_entry_point: Optional[str]
115
- connection_graph_summary: Dict[str, Any]
116
- current_score_estimate: float
117
- ```
118
-
119
- Each `PacketRecord` contains fields such as:
120
-
121
- - `packet_id`
122
- - `src_ip`
123
- - `dst_ip`
124
- - `src_port`
125
- - `dst_port`
126
- - `protocol`
127
- - `payload_size`
128
- - `ttl`
129
- - `flags`
130
- - `payload_preview`
131
- - `full_payload` when revealed
132
-
133
- ## Reward and Grading
134
-
135
- The reward function is shaped across the trajectory instead of only rewarding the final step.
136
-
137
- Positive signal is given for:
138
-
139
- - first-time malicious packet inspection
140
- - correct suspicious flags
141
- - good session grouping overlap
142
- - correct entry-point identification
143
- - correct final report outcomes
144
-
145
- Penalties are applied for:
146
-
147
- - repeated inspection
148
- - duplicate flagging
149
- - false positives
150
- - invalid or low-quality actions
151
-
152
- Both step reward and score stay in the `[0.0, 1.0]` range.
153
-
154
- Deterministic grading is driven by the JSON answer keys in `pcaps/`, which include:
155
-
156
- - `malicious_packets`
157
- - `packet_roles`
158
- - `sessions`
159
- - `session_roles`
160
- - `entry_point`
161
-
162
- The main logic lives in:
163
-
164
- - `src/reward.py`
165
- - `src/pcap_generator.py`
166
- - `server/network_forensics_environment.py`
167
-
168
- ## Baseline Inference
169
-
170
- The baseline runner is `inference.py`.
171
-
172
- It:
173
-
174
- - uses the OpenAI client for all model calls
175
- - supports `local`, `server`, and `docker` execution modes
176
- - prints `[START]`, `[STEP]`, and `[END]` lines in benchmark-friendly format
177
- - runs `easy`, `medium`, and `hard` sequentially
178
-
179
- Environment variables commonly used by the baseline:
180
-
181
- - `API_BASE_URL`
182
- - `MODEL_NAME`
183
- - `API_KEY` or `HF_TOKEN`
184
- - `NETWORK_FORENSICS_ENV_MODE`
185
- - `ENV_BASE_URL`
186
- - `LOCAL_IMAGE_NAME`
187
-
188
- ### Baseline Status
189
-
190
- Current local checks confirm:
191
-
192
- - task enumeration works
193
- - reward and score stay in `[0.0, 1.0]`
194
- - `easy`, `medium`, and `hard` all execute end to end
195
-
196
- Exact scores depend on the configured model endpoint.
197
-
198
- ## Running Locally
199
-
200
- Install dependencies:
201
-
202
- ```bash
203
- uv sync
204
- ```
205
-
206
- Run the OpenEnv server:
207
-
208
- ```bash
209
- uvicorn server.app:app --host 0.0.0.0 --port 8000
210
- ```
211
-
212
- Available endpoints:
213
-
214
- - `/health`
215
- - `/docs`
216
- - `/ws`
217
- - `/web`
218
- - `/demo`
219
-
220
- Run the baseline:
221
-
222
- ```bash
223
- python inference.py
224
- ```
225
-
226
- ## Docker
227
-
228
- The deployment Dockerfile is:
229
-
230
- - `server/Dockerfile`
231
-
232
- Build and run from the repository root:
233
-
234
- ```bash
235
- docker build -t network-forensics-env -f network_forensics/server/Dockerfile network_forensics
236
- docker run -p 8000:8000 network-forensics-env
237
- ```
238
-
239
- This container path has been verified to:
240
-
241
- - build successfully
242
- - start successfully
243
- - return `200 OK` from `/health`
244
-
245
- ## Hugging Face Space Deployment
246
-
247
- This project is configured as a Docker-based OpenEnv Space through `openenv.yaml`.
248
-
249
- Deploy with:
250
-
251
- ```bash
252
- openenv validate
253
- openenv push
254
- ```
255
-
256
- Expected Space endpoints:
257
-
258
- - `/health`
259
- - `/docs`
260
- - `/ws`
261
- - `/web`
262
- - `/demo`
263
-
264
- ## Connecting From Python
265
-
266
- Connect to a running server:
267
-
268
- ```python
269
- from network_forensics import NetworkForensicsAction, NetworkForensicsEnv
270
-
271
- with NetworkForensicsEnv(base_url="http://localhost:8000") as env:
272
- result = env.reset(task_id="easy")
273
- result = env.step(
274
- NetworkForensicsAction(
275
- action_type="inspect_packet",
276
- packet_id="pkt_0008",
277
- )
278
- )
279
- ```
280
-
281
- Connect to a deployed environment:
282
-
283
- ```python
284
- from network_forensics import NetworkForensicsAction, NetworkForensicsEnv
285
-
286
- with NetworkForensicsEnv.from_env("<hf-username>/<hf-repo-name>") as env:
287
- result = env.reset(task_id="medium")
288
- result = env.step(
289
- NetworkForensicsAction(
290
- action_type="flag_as_suspicious",
291
- packet_id="pkt_0008",
292
- )
293
- )
294
- ```
295
-
296
- ## Dataset Build Pipeline
297
-
298
- Task PCAPs and answer keys are generated from labeled flow data using:
299
-
300
- - `scripts/build_task_pcaps.py`
301
-
302
- That script writes:
303
-
304
- - `pcaps/easy_task.pcap`
305
- - `pcaps/easy_task.json`
306
- - `pcaps/medium_task.pcap`
307
- - `pcaps/medium_task.json`
308
- - `pcaps/hard_task.pcap`
309
- - `pcaps/hard_task.json`
310
-
311
- ## Repository Structure
312
-
313
- ```text
314
- network_forensics/
315
- β”œβ”€β”€ .dockerignore
316
- β”œβ”€β”€ .gitignore
317
- β”œβ”€β”€ __init__.py
318
- β”œβ”€β”€ client.py
319
- β”œβ”€β”€ inference.py
320
- β”œβ”€β”€ models.py
321
- β”œβ”€β”€ openenv.yaml
322
- β”œβ”€β”€ pcaps/
323
- β”œβ”€β”€ pyproject.toml
324
- β”œβ”€β”€ README.md
325
- β”œβ”€β”€ scripts/
326
- β”‚ └── build_task_pcaps.py
327
- β”œβ”€β”€ server/
328
- β”‚ β”œβ”€β”€ app.py
329
- β”‚ β”œβ”€β”€ Dockerfile
330
- β”‚ └── network_forensics_environment.py
331
- └── src/
332
- β”œβ”€β”€ pcap_generator.py
333
- β”œβ”€β”€ reward.py
334
- └── tasks/
335
- β”œβ”€β”€ easy.py
336
- β”œβ”€β”€ medium.py
337
- └── hard.py
338
- ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: Network Forensics Environment
3
  emoji: "πŸ›°οΈ"
 
7
  sdk_version: "1.0.0"
8
  pinned: false
9
  app_port: 8000
10
+ base_path: /
11
  tags:
12
  - openenv
13
+ - rl-environment
14
+ - network-security
15
  ---
16
 
17
  # Network Forensics Environment
18
+
19
+ `network_forensics` is an OpenEnv benchmark for packet triage and intrusion investigation. It simulates a real analyst workflow: inspect traffic, flag suspicious packets, group related activity into sessions, classify attack patterns, identify the likely entry point, and submit a final report.
20
+
21
+ The environment is backed by generated PCAP traces and deterministic JSON answer keys, so agents can be evaluated consistently while still solving a real-world security analysis task.
22
+
23
+ ## Motivation
24
+
25
+ Security analysts routinely ask:
26
+
27
+ - Which packets are suspicious?
28
+ - Which packets belong to the same malicious session?
29
+ - What kind of attack is this?
30
+ - Which packet looks like the initial compromise or entry point?
31
+
32
+ This environment turns that workflow into a reproducible benchmark for LLM and RL-style agents.
33
+
34
+ ## Tasks
35
+
36
+ The benchmark includes three deterministic tasks with increasing difficulty.
37
+
38
+ ### Easy
39
+
40
+ - Files: `pcaps/easy_task.pcap`, `pcaps/easy_task.json`
41
+ - Theme: DDoS-heavy traffic mixed with benign flows
42
+ - Goal: recover the main malicious traffic and dominant attack sessions
43
+
44
+ ### Medium
45
+
46
+ - Files: `pcaps/medium_task.pcap`, `pcaps/medium_task.json`
47
+ - Theme: mixed web attacks
48
+ - Attack families: `web_bruteforce`, `web_xss`, `web_sql_injection`
49
+ - Goal: separate multiple web attack sessions and tag them correctly
50
+
51
+ ### Hard
52
+
53
+ - Files: `pcaps/hard_task.pcap`, `pcaps/hard_task.json`
54
+ - Theme: noisy denial-of-service and exploitation traffic
55
+ - Attack families: `dos_hulk`, `dos_goldeneye`, `dos_slowloris`, `dos_slowhttptest`, `heartbleed`
56
+ - Goal: recover multiple malicious sessions, avoid false positives, and identify the root cause accurately
57
+
58
+ ## Action Space
59
+
60
+ The environment uses the `NetworkForensicsAction` Pydantic model:
61
+
62
+ ```python
63
+ class NetworkForensicsAction(Action):
64
+ action_type: str
65
+ packet_id: Optional[str] = None
66
+ packet_ids: Optional[List[str]] = None
67
+ session_name: Optional[str] = None
68
+ pattern_type: Optional[str] = None
69
+ claimed_entry_point: Optional[str] = None
70
+ ```
71
+
72
+ Supported actions:
73
+
74
+ - `inspect_packet`: reveal the payload of `packet_id`
75
+ - `flag_as_suspicious`: mark `packet_id` as suspicious
76
+ - `group_into_session`: group `packet_ids` under `session_name`
77
+ - `tag_pattern`: assign an attack label to a session
78
+ - `identify_entry_point`: claim the likely first malicious packet
79
+ - `submit_report`: end the episode and trigger deterministic final grading
80
+
81
+ ## Observation Space
82
+
83
+ The environment returns `NetworkForensicsObservation`:
84
+
85
+ ```python
86
+ class NetworkForensicsObservation(Observation):
87
+ step_number: int
88
+ steps_remaining: int
89
+ total_packets: int
90
+ visible_packets: List[PacketRecord]
91
+ flagged_packet_ids: List[str]
92
+ grouped_sessions: Dict[str, List[str]]
93
+ tagged_patterns: Dict[str, str]
94
+ claimed_entry_point: Optional[str]
95
+ connection_graph_summary: Dict[str, Any]
96
+ current_score_estimate: float
97
+ ```
98
+
99
+ Each `PacketRecord` includes fields such as:
100
+
101
+ - `packet_id`
102
+ - `src_ip`
103
+ - `dst_ip`
104
+ - `src_port`
105
+ - `dst_port`
106
+ - `protocol`
107
+ - `ttl`
108
+ - `payload_size`
109
+ - `payload_preview`
110
+ - `full_payload` once revealed
111
+
112
+ ## Reward and Grading
113
+
114
+ The environment uses two complementary signals.
115
+
116
+ ### Shaped Step Reward
117
+
118
+ Dense reward is provided across the trajectory instead of only at the end.
119
+
120
+ Higher reward is given for:
121
+
122
+ - first-time malicious packet inspection
123
+ - correct suspicious flags
124
+ - high-overlap session grouping
125
+ - correct pattern tagging
126
+ - correct entry-point identification
127
+
128
+ Lower reward is given for undesirable behavior such as:
129
+
130
+ - repeated inspection
131
+ - duplicate flags
132
+ - poor grouping recall
133
+ - low-quality or incorrect actions
134
+
135
+ Both step reward and running score are normalized into `[0.0, 1.0]`.
136
+
137
+ ### Deterministic Final Grader
138
+
139
+ The final `submit_report` action runs a deterministic audit against the task JSON answer key.
140
+
141
+ The final score is:
142
+
143
+ ```text
144
+ 0.3 * precision + 0.4 * recall + 0.3 * logic
145
+ ```
146
+
147
+ Where:
148
+
149
+ - `precision`: how cleanly the agent flagged malicious packets
150
+ - `recall`: how much malicious traffic the agent actually recovered
151
+ - `logic`: whether the agent linked sessions, tags, and entry point correctly for the task difficulty
152
+
153
+ Difficulty-specific success rules are enforced:
154
+
155
+ - `easy`: strong malicious-packet recall
156
+ - `medium`: strong recall plus meaningful session overlap and acceptable precision
157
+ - `hard`: all of the above plus correct root-cause identification
158
+
159
+ Ground truth comes from the JSON files in `pcaps/`, including:
160
+
161
+ - `malicious_packets`
162
+ - `packet_roles`
163
+ - `sessions`
164
+ - `session_roles`
165
+ - `entry_point`
166
+
167
+ Core implementation lives in:
168
+
169
+ - `src/reward.py`
170
+ - `src/pcap_generator.py`
171
+ - `server/network_forensics_environment.py`
172
+
173
+ ## Baseline Inference
174
+
175
+ The baseline runner is `inference.py`.
176
+
177
+ It:
178
+
179
+ - uses the OpenAI-compatible client for model calls
180
+ - supports `server` and `docker` execution modes
181
+ - prints `[START]`, `[STEP]`, and `[END]` logs
182
+ - runs `easy`, `medium`, and `hard` sequentially
183
+
184
+ Important environment variables:
185
+
186
+ - `API_BASE_URL`
187
+ - `MODEL_NAME`
188
+ - `OPENAI_API_KEY`, `API_KEY`, or `HF_TOKEN`
189
+ - `NETWORK_FORENSICS_ENV_MODE`
190
+ - `ENV_BASE_URL`
191
+ - `LOCAL_IMAGE_NAME`
192
+
193
+ ### Example Baseline Results
194
+
195
+ Observed recent runs:
196
+
197
+ - `openai/gpt-oss-120b`
198
+ - `easy`: success `true`, score `0.64`
199
+ - `medium`: success `false`, score `0.55`
200
+ - `hard`: success `true`, score `0.63`
201
+ - `mistralai/mistral-small-4-119b-2603`
202
+ - `easy`: success `false`, score `0.46`
203
+ - `medium`: success `false`, score `0.57`
204
+ - `hard`: success `true`, score `0.60`
205
+
206
+ These examples show that the environment and final grader are sensitive to model behavior rather than returning a constant score.
207
+
208
+ ## Setup and Local Usage
209
+
210
+ Install dependencies:
211
+
212
+ ```bash
213
+ uv sync
214
+ ```
215
+
216
+ Start the server:
217
+
218
+ ```bash
219
+ uv run server
220
+ ```
221
+
222
+ Or with uvicorn directly:
223
+
224
+ ```bash
225
+ uvicorn server.app:app --host 0.0.0.0 --port 8000
226
+ ```
227
+
228
+ Useful endpoints:
229
+
230
+ - `/` for the custom Gradio analyst UI
231
+ - `/web` redirects to `/`
232
+ - `/health`
233
+ - `/docs`
234
+ - `/reset`
235
+ - `/step`
236
+ - `/state`
237
+ - `/schema`
238
+ - `/ws`
239
+
240
+ Run the baseline against the local server:
241
+
242
+ ```bash
243
+ NETWORK_FORENSICS_ENV_MODE=server ENV_BASE_URL=http://localhost:8000 python inference.py
244
+ ```
245
+
246
+ On Windows PowerShell:
247
+
248
+ ```powershell
249
+ $env:NETWORK_FORENSICS_ENV_MODE="server"
250
+ $env:ENV_BASE_URL="http://localhost:8000"
251
+ py .\inference.py
252
+ ```
253
+
254
+ ## Docker
255
+
256
+ The deployment Dockerfile is:
257
+
258
+ - `server/Dockerfile`
259
+
260
+ From the cloned `network_forensics` repository root:
261
+
262
+ ```bash
263
+ docker build -t network-forensics-env -f server/Dockerfile .
264
+ docker run -p 8000:8000 network-forensics-env
265
+ ```
266
+
267
+ This is the canonical OpenEnv and Hugging Face Space deployment path.
268
+
269
+ ## Hugging Face Space Deployment
270
+
271
+ This project is configured as a Docker-based OpenEnv Space through `openenv.yaml`.
272
+
273
+ Validate locally:
274
+
275
+ ```bash
276
+ openenv validate
277
+ ```
278
+
279
+ Push to Hugging Face using the custom UI rather than the default OpenEnv web interface:
280
+
281
+ ```bash
282
+ openenv push --no-interface
283
+ ```
284
+
285
+ On the deployed Space:
286
+
287
+ - `/` serves the custom Gradio analyst console
288
+ - `/web` redirects to `/`
289
+ - the OpenEnv API remains available for agent evaluation
290
+
291
+ ## Connecting From Python
292
+
293
+ Connect to a running local or remote server:
294
+
295
+ ```python
296
+ from network_forensics import NetworkForensicsAction, NetworkForensicsEnv
297
+
298
+ with NetworkForensicsEnv(base_url="http://localhost:8000") as env:
299
+ result = env.reset(task_id="easy")
300
+ result = env.step(
301
+ NetworkForensicsAction(
302
+ action_type="inspect_packet",
303
+ packet_id="pkt_0008",
304
+ )
305
+ )
306
+ ```
307
+
308
+ Connect to a deployed Hugging Face Space:
309
+
310
+ ```python
311
+ from network_forensics import NetworkForensicsAction, NetworkForensicsEnv
312
+
313
+ with NetworkForensicsEnv.from_env("<hf-username>/<hf-repo-name>") as env:
314
+ result = env.reset(task_id="medium")
315
+ result = env.step(
316
+ NetworkForensicsAction(
317
+ action_type="flag_as_suspicious",
318
+ packet_id="pkt_0008",
319
+ )
320
+ )
321
+ ```
322
+
323
+ ## Dataset Build Pipeline
324
+
325
+ Task PCAPs and answer keys are generated from labeled flow data using:
326
+
327
+ - `scripts/build_task_pcaps.py`
328
+
329
+ That script writes:
330
+
331
+ - `pcaps/easy_task.pcap`
332
+ - `pcaps/easy_task.json`
333
+ - `pcaps/medium_task.pcap`
334
+ - `pcaps/medium_task.json`
335
+ - `pcaps/hard_task.pcap`
336
+ - `pcaps/hard_task.json`
337
+
338
+ ## Repository Structure
339
+
340
+ ```text
341
+ network_forensics/
342
+ β”œβ”€β”€ .dockerignore
343
+ β”œβ”€β”€ .gitignore
344
+ β”œβ”€β”€ __init__.py
345
+ β”œβ”€β”€ client.py
346
+ β”œβ”€β”€ inference.py
347
+ β”œβ”€β”€ models.py
348
+ β”œβ”€β”€ openenv.yaml
349
+ β”œβ”€β”€ pcaps/
350
+ β”œβ”€β”€ pyproject.toml
351
+ β”œβ”€β”€ README.md
352
+ β”œβ”€β”€ scripts/
353
+ β”‚ └── build_task_pcaps.py
354
+ β”œβ”€β”€ server/
355
+ β”‚ β”œβ”€β”€ app.py
356
+ β”‚ β”œβ”€β”€ Dockerfile
357
+ β”‚ β”œβ”€β”€ gradio_ui.py
358
+ β”‚ └── network_forensics_environment.py
359
+ └── src/
360
+ β”œβ”€β”€ pcap_generator.py
361
+ β”œβ”€β”€ reward.py
362
+ └── tasks/
363
+ β”œβ”€β”€ easy.py
364
+ β”œβ”€β”€ medium.py
365
+ └── hard.py
366
+ ```
inference.py CHANGED
@@ -19,8 +19,8 @@ from models import NetworkForensicsAction
19
  load_dotenv(Path(__file__).parent / ".env")
20
 
21
  API_BASE_URL = os.getenv("API_BASE_URL")
22
- MODEL_NAME = os.getenv("MODEL_NAME")
23
- API_KEY = os.getenv("API_KEY") or os.getenv("HF_TOKEN")
24
  LOCAL_IMAGE_NAME = os.getenv("LOCAL_IMAGE_NAME", "network-forensics-env:latest")
25
  ENV_MODE = (os.getenv("NETWORK_FORENSICS_ENV_MODE") or os.getenv("ENV_MODE") or "docker").lower()
26
  ENV_BASE_URL = os.getenv("ENV_BASE_URL", "http://localhost:8000")
@@ -50,10 +50,8 @@ def validate_config() -> None:
50
  missing = []
51
  if not API_BASE_URL:
52
  missing.append("API_BASE_URL")
53
- if not MODEL_NAME:
54
- missing.append("MODEL_NAME")
55
  if not API_KEY:
56
- missing.append("API_KEY")
57
  if missing:
58
  raise RuntimeError(f"Missing required environment variables: {', '.join(missing)}")
59
  if ENV_MODE not in {"server", "docker"}:
 
19
  load_dotenv(Path(__file__).parent / ".env")
20
 
21
  API_BASE_URL = os.getenv("API_BASE_URL")
22
+ MODEL_NAME = os.getenv("MODEL_NAME", "openai/gpt-oss-120b")
23
+ API_KEY = os.getenv("OPENAI_API_KEY") or os.getenv("API_KEY") or os.getenv("HF_TOKEN")
24
  LOCAL_IMAGE_NAME = os.getenv("LOCAL_IMAGE_NAME", "network-forensics-env:latest")
25
  ENV_MODE = (os.getenv("NETWORK_FORENSICS_ENV_MODE") or os.getenv("ENV_MODE") or "docker").lower()
26
  ENV_BASE_URL = os.getenv("ENV_BASE_URL", "http://localhost:8000")
 
50
  missing = []
51
  if not API_BASE_URL:
52
  missing.append("API_BASE_URL")
 
 
53
  if not API_KEY:
54
+ missing.append("OPENAI_API_KEY/API_KEY/HF_TOKEN")
55
  if missing:
56
  raise RuntimeError(f"Missing required environment variables: {', '.join(missing)}")
57
  if ENV_MODE not in {"server", "docker"}: