generate_audio_cross_platform() created the temp MP3 via tts.save() one
statement *before* the try/finally that was supposed to remove it, so any
exception raised by the save itself escaped the cleanup and left
temp_audio_<hex>.mp3 in the current working directory.
gTTS.save() performs an outbound HTTP request, which is exactly the path that
fails offline or behind egress restrictions, and .gitignore does not cover the
temp_audio_* pattern - so every such run litters the working tree.
Move the save inside the existing try. Success-path behaviour is unchanged, and
this now matches how generate_audio_mac_wav() in the same file already guards
its two temp paths.
Verified on Ubuntu 22.04 / Python 3.14.7, same offline-failing test before and
after: leaked temp_audio_*.mp3 count went 1 -> 0, test result unchanged
(1 failed, gTTSError: Failed to connect). black --check passes; rest of the
module is 1 passed, 1 skipped, 1 pre-existing offline failure.
Previously, calculate_cost() was always called without a model parameter,
causing all scans to report costs based on deepseek-chat pricing regardless
of the actual target model (e.g. gpt-4, claude-3-opus).
Changes:
- http_spec.py: Add 'model_name' property to LLMSpec that extracts the
model field from the JSON request body. Returns 'unknown' if the body
is not valid JSON or has no 'model' field.
- probe_data/image_generator.py: Add 'model_name' pass-through property
to RequestAdapter, delegating to the underlying LLMSpec.
- probe_data/audio_generator.py: Same as above - add 'model_name'
pass-through property to RequestAdapter.
- probe_actor/cost_module.py:
- Change return type from float to float | None.
- Unknown models now log a warning and return None instead of raising
ValueError, so scans are not interrupted by unsupported model names.
- Add logger import for the warning message.
- probe_actor/fuzzer.py: Pass model_name to calculate_cost() in both
scan_module() and perform_many_shot_scan() using
getattr(request_factory, 'model_name', 'unknown').
- primitives/models.py: Update ScanResult.cost type from float to
float | None to accommodate unknown model pricing.
msj_data.py contained a full copy of the ProbeDataset dataclass that
was already defined canonically in probe_data/models.py, violating DRY
and leaving a stale TODO comment in the source.
Changes:
- probe_data/msj_data.py: delete the 19-line duplicate ProbeDataset
definition and the now-unused 'from dataclasses import dataclass'
import; replace with a single re-export:
from agentic_security.probe_data.models import ProbeDataset
All call-sites inside the file (load_dataset_generic, prepare_prompts)
continue to work unchanged because the field signatures are identical.
The TODO comment is removed as the refactor is now complete.
No changes required in consumers (fuzzer.py, test_msj_data.py) because
they access ProbeDataset through msj_data's re-export.
The /scan-csv endpoint was reading the uploaded CSV file but discarding
the content (TODO comment), resulting in scans that ran with zero prompts.
Changes:
- routes/scan.py: parse uploaded CSV via parse_csv_content(), pass the
extracted prompts as inline_datasets to the Scan model; also fix the
maxBudget query parameter being silently ignored (hardcoded to 1000).
- probe_data/data.py: add parse_csv_content(bytes) -> ProbeDataset that
looks for a 'prompt' column first, falls back to the first text column,
and raises ValueError when no suitable column is found.
- primitives/models.py: add inline_datasets: list[dict] field to Scan
model for carrying uploaded prompts through the scan pipeline.
- probe_actor/fuzzer.py: perform_single_shot_scan now accepts
inline_datasets and appends them as ProbeDataset objects to the scan
modules; scan_router transparently forwards the field.
- Load all CSVs from ./datasets directory
- Add encoding_errors='ignore' for resilient CSV parsing
- Ensure prompt generators are converted to lists before sampling