pyupgrade --py314-plus rewrites the quoted return annotation added in #345,
failing pre-commit. The project requires Python 3.14, where annotations are
evaluated lazily (PEP 649), so the unquoted form imports fine.
poetry.toml sets virtualenvs.create=false, so pipx poetry installed into
the runner's system Python 3.12 and failed uninstalling Debian packages.
Force venv creation in CI via POETRY_VIRTUALENVS_CREATE. Drop the poetry
cache from release, which never creates a venv.
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.
Install poetry before setup-python and enable the poetry cache so
setup-python activates the 3.14 interpreter. With virtualenvs.create=false
poetry otherwise installs into the runner's system Python 3.12 and fails
uninstalling Debian dist-packages.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bk6CFfaN3YJFZ6yztQja7D
ExecutorMetrics.get_stats picked the p95 element with
int(n * 0.95), which collapses to the last index (the maximum)
for any batch of 20 or fewer requests, so the reported p95 was
actually the worst-case latency for typical scan batches. Use
ceil(n * 0.95) - 1, the standard nearest-rank index.
record_failure ignored the half_open state: the minimum-sample gate
(total >= 10) and the success counter meant a failure during
probation neither re-opened the circuit nor prevented it from
closing. One failure followed by three successes closed the circuit
with the failure forgotten. Trip back to open on the first half-open
failure, with fresh counters.
PIIDetector and SandboxEscapeDetector were wired directly in
probe_actor/refusal.py and the refusal classifier manager was populated from
a hardcoded list, so the only way to toggle a bundled detector or add an
organization-specific signature was to patch the module.
Add a DetectorRegistry mapping plugin names to factories, assembled from an
agentic_security.toml [detectors] section via build_from_config. Custom
detectors load by import path ("pkg.module:ClassName"). refusal.py gains
build_refusal_manager(config=None) reading the [detectors] table; all public
symbols are preserved. Built-in leak detectors ship registered but disabled,
so default refusal_heuristic behaviour is unchanged.
Closes#82
Signed-off-by: Devam Shah <devamshah91@gmail.com>
CORS: drop allow_credentials=True. With allow_origins=['*'] Starlette reflects
the request Origin and emits Access-Control-Allow-Credentials: true on any
credentialed request — a reflect-any-origin hole. The app authenticates with
Bearer tokens in the Authorization header, so credentialed CORS was never needed.
/icons/{icon_name}: validate against a strict allowlist (re.fullmatch
[A-Za-z0-9._-]+\.png) and assert the resolved path stays inside ICONS_DIR before
any filesystem write or outbound fetch. Closes the path-traversal / arbitrary-write
(CWE-22) and controlled-URL (CWE-73) surface on the unsanitized icon_name.
Adds CORS middleware tests and icon-name validation tests (traversal, encoded
slash, null byte, trailing newline, wrong extension).
Closes#298
Signed-off-by: Devam Shah <devamshah91@gmail.com>