Skip to content

Fix plugin-first runtime boundaries and CodeQL findings - #46

Draft
Iranman wants to merge 20 commits into
mainfrom
fix/plugin-first-runtime-and-codeql
Draft

Fix plugin-first runtime boundaries and CodeQL findings#46
Iranman wants to merge 20 commits into
mainfrom
fix/plugin-first-runtime-and-codeql

Conversation

@Iranman

@Iranman Iranman commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Summary

  • Routes pre-import AcoustID lookup through the remote Beets engine control agent on port 8338 (POST /audio/acoustid-lookup), eliminating direct fpcalc execution and direct api.acoustid.org HTTP calls in the web manager.
  • Preserves structured AcoustID match states (matched, no_match, unavailable, invalid_media, timeout, provider_error, analysis_error) end-to-end through the web manager workflow.
  • Wires ACOUSTID_API_KEY to the engine container environment, failing closed when missing without crashing unrelated setup or diagnostics.
  • Restores ffmpeg in the web manager for yt-dlp source acquisition postprocessing while removing git and plugin-only dependencies.
  • Establishes durable plugin-first Beets architecture policy across AGENTS.md, CLAUDE.md, docs/AGENT_WORKFLOW.md, docs/ARCHITECTURE.md, and REVIEW.md.
  • Hardens SQL allowlists, raw-query parameters, error sanitization, and filesystem path boundaries (resolve_safe_path) in control agent endpoints.

Branch topology -- current with main

Rebased onto main @ 92a7ea62274eb9de3e3a4aafd4e18152e7235109 (which includes PR #55's non-app-backend CodeQL remediation and PR #56's app.py stack-trace-exposure remediation, both separate SEC-002 waves). docs/TECHNICAL_DEBT.md's SEC-002 section (Waves 1-3, full alert accounting, remaining backlog) is preserved verbatim from main; this PR's own ARCH-007 escalation, ARCH-013, and ARCH-014 entries are appended after it, each exactly once. Backup ref backup/pr46-before-main-92a7ea6-20260730-184625 preserved before the rewrite.

Codex independent review -- REQUEST CHANGES addressed

An independent Codex review of the previously frozen head (f1b44ea1...) returned REQUEST CHANGES with two P1 blockers and two P3 findings, all now fixed:

P1 -- base invalidated: main had advanced past this branch's base. Fixed by the rebase above.

P1 -- configured AcoustID provider path crashed: backend/beets_control_agent.py used urllib.request.Request/urlopen and urllib.error.HTTPError but only imported urllib.parse. This worked by accident in the full test suite (another test module's own import had already bound the attribute onto the shared urllib package object) but crashed with AttributeError: module 'urllib' has no attribute 'request' in any genuinely isolated process, including the real container -- a valid file would pass auth/path-validation/fingerprinting, then crash the instant a configured key reached the actual provider request. Fixed: added the missing imports and restructured exception handling so HTTPError/URLError both map to provider_error immediately, a genuine TimeoutError still retries once before reporting timeout, and nothing echoes raw provider/exception text. Verified three independent ways: (1) AcoustidLookupProviderRouteTests, mocking only urllib.request.urlopen, exercises the real handler through matched/no_match/HTTPError/URLError/timeout/malformed-JSON; (2) AcoustidLookupIsolatedImportTests spawns a genuinely fresh subprocess importing only this module -- proven to fail with the exact reported AttributeError against the unfixed file; (3) live in a disposable container with a real synthetic ACOUSTID_API_KEY, real fpcalc fingerprinting a generated WAV file, and api.acoustid.org DNS-redirected to an unreachable local address (never contacts the real service) -- the request reached a clean 502 provider_error, and the engine logs contain zero AttributeError/traceback lines.

P3 -- stale documentation: fixed 4 incorrect ARCH-012 -> ARCH-013 AcoustID references (README.md, docs/CONFIGURATION.md, backend/beets_control_agent.py, tests/test_audio_identity_pipeline.py); removed a README claim of a "shared, rate-limited test key" fallback that directly contradicted the correct statement three lines above it in the same file (no such fallback exists anywhere in this codebase); corrected ARCH-014 to state alerts #404/#405 were dismissed as false positives (not "remain open").

P3 -- media path disclosure in helper log: helpers_mb._acoustid_lookup_status()'s failure path logged the complete media path and raw exception text (which can itself embed a provider URL or query string). Fixed: logs a 12-character path hash plus the exception's type name only; removed the same raw text from the returned dict's error field (no caller read it). Regression test captures real log output via assertLogs with a distinctive path and an exception embedding a fake secret, and asserts both are absent while a useful diagnostic remains -- verified to fail against the prior implementation.

Validation (frozen head 66c2566efe5db21cdf52db10fa7335d1c4f8d0a7)

  • Backend Test Suite: 1608 total tests, OK, 0 failures, 0 errors, 7 skipped.
  • Frontend: typecheck/lint/build clean; npm audit --audit-level=high 0 vulnerabilities.
  • Security & Compose: secret scan, compose-security validator, git diff --check, both docker compose config files -- all clean.
  • Docker: both images independently rebuilt at this exact head; web-manager boundary assertion and Beets engine verifier both passed; live disposable two-container matrix (synthetic media/tokens, no production data, no contact with the real AcoustID service) covered the full path-security set plus the configured-provider-request path described above.
  • Ordinary CI: all 7 distinct jobs pass on this exact head across both push and pull_request triggers (14 executions).

CodeQL -- analyzed on the exact frozen head, zero open alerts

Analyzed SHA: 66c2566efe5db21cdf52db10fa7335d1c4f8d0a7
Analysis IDs: 1552519861 (python, 2 results), 1552517750 (javascript-typescript, 0), 1552516926 (actions, 0)

Alerts #404 and #405 (py/path-injection, the .exists()/.is_file() sinks in /audio/acoustid-lookup) were re-verified against the unchanged data path and remain dismissed/false positive -- GitHub's alert-fingerprinting continues to carry the dismissal forward automatically across rebases, since the underlying sink code is unchanged (only shifted by 2 lines from the new imports). 0 open PR-introduced alerts. Aggregate CodeQL check: green.

PR #50 remains closed, not merged. Issue #51 (ARCH-007) remains open and separate.

Remaining limitations

@Iranman
Iranman force-pushed the fix/plugin-first-runtime-and-codeql branch from 538b445 to 0b11a26 Compare July 30, 2026 14:51
Comment thread backend/beets_control_agent.py Outdated
self._send_json(403, {"ok": False, "status": "analysis_error", "error": f"Access denied for path outside allowed roots: {file_path}"})
return

if not safe_file_path.exists(): # codeql[py/path-injection] -- sanitized by resolve_safe_path() above
Comment thread backend/beets_control_agent.py Outdated
self._send_json(404, {"ok": False, "status": "invalid_media", "error": f"File not found: {file_path}"})
return

if not safe_file_path.is_file(): # codeql[py/path-injection] -- sanitized by resolve_safe_path() above
Iranman added a commit that referenced this pull request Jul 30, 2026
…view

Independent final review of PR #46 found and fixed:

- backend/beets_control_agent.py had TWO conflicting definitions of
  is_safe_path(): a broken one (line ~488, calling
  resolve_safe_path(...) is not None -- which never returns None,
  since resolve_safe_path() raises UnsafePathError instead, so this
  would crash rather than return False for unsafe input) left over
  from commit 0b11a26's rebase reconciliation, silently shadowed by
  the correct try/except version already present on the rebased base
  (refactor/external-beets-engine @ 125752d). Removed the broken,
  dead first definition and its accompanying dead
  `except Exception: return None` duplicate clause in
  _path_is_within(), which was unreachable but violated that
  function's own bool return contract.
- Two duplicate bare `return` statements (tag-write's "Unsupported tag
  field" branch, and the new /audio/acoustid-lookup endpoint's
  "Path is not a regular file" branch) -- both dead code from the same
  rebase, removed.
- /audio/acoustid-lookup echoed the raw request path (`file_path`) into
  403/404/400 error bodies and a raw Python exception string into a
  500 body, inconsistent with every other sink in this file. Changed
  to the same generic-message pattern already used by
  /files/delete, /files/move, /commands/execute, etc.
- Removed the non-functional `# codeql[py/path-injection] -- sanitized
  by resolve_safe_path() above` comments: GitHub Code Scanning has no
  inline source-comment suppression mechanism, so these had zero
  actual effect and misrepresented the alert as handled. A live
  temporary CodeQL-validation PR against main (closed without merging)
  confirmed 2 open py/path-injection alerts remain on these exact
  lines for this exact head. Documented as ARCH-014 in
  docs/TECHNICAL_DEBT.md with the real remediation options (advanced
  model pack, blocked while Default Setup is enabled; or an
  owner-reviewed dismissal).
- Escalated ARCH-007 with a confirmed, more severe finding unrelated
  to this PR's own diff: backend/beets_client.py's raw_sqlite_query()
  unconditionally raises (already true on the rebased base, not
  introduced here), so every one of the ~18 remaining
  _db()/con.execute(...) call sites in app.py always raises or is
  silently swallowed by a broad except. Flagged P0 for separate
  follow-up; out of scope for this PR to fix.

All new tests pass; full suite unaffected (1553 tests, OK, skipped=6
locally / 61 on Linux CI -- unchanged).
Iranman added a commit that referenced this pull request Jul 30, 2026
…view

Independent final review of PR #46 found and fixed:

- backend/beets_control_agent.py had TWO conflicting definitions of
  is_safe_path(): a broken one (line ~488, calling
  resolve_safe_path(...) is not None -- which never returns None,
  since resolve_safe_path() raises UnsafePathError instead, so this
  would crash rather than return False for unsafe input) left over
  from commit 0b11a26's rebase reconciliation, silently shadowed by
  the correct try/except version already present on the rebased base
  (refactor/external-beets-engine @ 125752d). Removed the broken,
  dead first definition and its accompanying dead
  `except Exception: return None` duplicate clause in
  _path_is_within(), which was unreachable but violated that
  function's own bool return contract.
- Two duplicate bare `return` statements (tag-write's "Unsupported tag
  field" branch, and the new /audio/acoustid-lookup endpoint's
  "Path is not a regular file" branch) -- both dead code from the same
  rebase, removed.
- /audio/acoustid-lookup echoed the raw request path (`file_path`) into
  403/404/400 error bodies and a raw Python exception string into a
  500 body, inconsistent with every other sink in this file. Changed
  to the same generic-message pattern already used by
  /files/delete, /files/move, /commands/execute, etc.
- Removed the non-functional `# codeql[py/path-injection] -- sanitized
  by resolve_safe_path() above` comments: GitHub Code Scanning has no
  inline source-comment suppression mechanism, so these had zero
  actual effect and misrepresented the alert as handled. A live
  temporary CodeQL-validation PR against main (closed without merging)
  confirmed 2 open py/path-injection alerts remain on these exact
  lines for this exact head. Documented as ARCH-014 in
  docs/TECHNICAL_DEBT.md with the real remediation options (advanced
  model pack, blocked while Default Setup is enabled; or an
  owner-reviewed dismissal).
- Escalated ARCH-007 with a confirmed, more severe finding unrelated
  to this PR's own diff: backend/beets_client.py's raw_sqlite_query()
  unconditionally raises (already true on the rebased base, not
  introduced here), so every one of the ~18 remaining
  _db()/con.execute(...) call sites in app.py always raises or is
  silently swallowed by a broad except. Flagged P0 for separate
  follow-up; out of scope for this PR to fix.

All new tests pass; full suite unaffected (1553 tests, OK, skipped=6
locally / 61 on Linux CI -- unchanged).
@Iranman
Iranman force-pushed the fix/plugin-first-runtime-and-codeql branch from 3207bd8 to 819ac21 Compare July 30, 2026 16:10
@Iranman
Iranman changed the base branch from refactor/external-beets-engine to main July 30, 2026 16:10
Iranman added a commit that referenced this pull request Jul 30, 2026
Retargeting PR #46's base (refactor/external-beets-engine -> main) is a
GitHub "edited" PR event, which does not fall in the default trigger set
(opened, synchronize, reopened) for pull_request-triggered workflows,
including GitHub's own Default Setup CodeQL analysis. No analysis
appeared for this PR's head after 5 minutes of polling post-retarget.
This empty commit produces a genuine synchronize event so Default Setup
analyzes the current exact head under its now-current base (main),
without creating another temporary validation PR.
Comment thread backend/beets_control_agent.py Dismissed
Comment thread backend/beets_control_agent.py Dismissed
Iranman added a commit that referenced this pull request Jul 30, 2026
…view

Independent final review of PR #46 found and fixed:

- backend/beets_control_agent.py had TWO conflicting definitions of
  is_safe_path(): a broken one (line ~488, calling
  resolve_safe_path(...) is not None -- which never returns None,
  since resolve_safe_path() raises UnsafePathError instead, so this
  would crash rather than return False for unsafe input) left over
  from commit 0b11a26's rebase reconciliation, silently shadowed by
  the correct try/except version already present on the rebased base
  (refactor/external-beets-engine @ 125752d). Removed the broken,
  dead first definition and its accompanying dead
  `except Exception: return None` duplicate clause in
  _path_is_within(), which was unreachable but violated that
  function's own bool return contract.
- Two duplicate bare `return` statements (tag-write's "Unsupported tag
  field" branch, and the new /audio/acoustid-lookup endpoint's
  "Path is not a regular file" branch) -- both dead code from the same
  rebase, removed.
- /audio/acoustid-lookup echoed the raw request path (`file_path`) into
  403/404/400 error bodies and a raw Python exception string into a
  500 body, inconsistent with every other sink in this file. Changed
  to the same generic-message pattern already used by
  /files/delete, /files/move, /commands/execute, etc.
- Removed the non-functional `# codeql[py/path-injection] -- sanitized
  by resolve_safe_path() above` comments: GitHub Code Scanning has no
  inline source-comment suppression mechanism, so these had zero
  actual effect and misrepresented the alert as handled. A live
  temporary CodeQL-validation PR against main (closed without merging)
  confirmed 2 open py/path-injection alerts remain on these exact
  lines for this exact head. Documented as ARCH-014 in
  docs/TECHNICAL_DEBT.md with the real remediation options (advanced
  model pack, blocked while Default Setup is enabled; or an
  owner-reviewed dismissal).
- Escalated ARCH-007 with a confirmed, more severe finding unrelated
  to this PR's own diff: backend/beets_client.py's raw_sqlite_query()
  unconditionally raises (already true on the rebased base, not
  introduced here), so every one of the ~18 remaining
  _db()/con.execute(...) call sites in app.py always raises or is
  silently swallowed by a broad except. Flagged P0 for separate
  follow-up; out of scope for this PR to fix.

All new tests pass; full suite unaffected (1553 tests, OK, skipped=6
locally / 61 on Linux CI -- unchanged).
@Iranman
Iranman force-pushed the fix/plugin-first-runtime-and-codeql branch from 39fa5cc to f1b44ea Compare July 30, 2026 21:19
Iranman added a commit that referenced this pull request Jul 30, 2026
The 4 job-log carve-out alerts (#43/#46/#68/#83) plus a 5th instance
that surfaced after the delete_import_review_folder fix (#413, same
_delete_review_source_folder log sources, new alert number once the
sibling "error" field became a static string) are dismissed
individually on GitHub as won't-fix with the CLAUDE.md raw-debug-detail
justification, rather than left open -- the hard zero-open-PR-alerts
merge gate requires a real disposition. Corrects the dismissal count
(10, not 5) and remaining backlog (225, not 220).
Iranman added a commit that referenced this pull request Jul 30, 2026
* fix: sanitize app.py public exception responses (SEC-002 wave 3)

Independently traced and closed all 62 open py/stack-trace-exposure
alerts in app.py (scope agreed with repo owner; path-injection, ReDoS,
URL-substring, and other rule families in app.py remain out of scope
for a future wave, as do index.html/Playlists.tsx).

Genuine fixes (~48 call sites/helpers): the established pattern
throughout -- log the real exception server-side (app.logger.warning,
type name only, never the message) and return a fixed safe message.
Notable:
- /api/config GET/POST and /api/config/revert (the beets config.yaml
  file itself, the single most sensitive file in the container since
  it can hold provider credentials) previously leaked raw filesystem
  exception text on any read/write/backup failure.
- _classify_openai_error()'s fallback branch (f"AI provider error
  ({exc})") now returns only the exception type name; this closed
  every downstream alert that traced through the shared helper.
- _folder_cleanup_path()'s except-branch (f"Invalid path: {exc}") now
  returns a fixed message; this closed 7 downstream alerts across the
  folder-move/rename/merge maintenance route sharing this one source.
- _SpotifyFetchError's two raise sites used to wrap the underlying
  urllib/json exception in violation of the "never wrap another
  exception" rule; both now raise fixed messages.
- One yt-dlp cookie smoke-test probe error wasn't yet routed through
  the codebase's existing _redact_security_text() helper (already used
  elsewhere for auth-rejection reasons); now is.

Proven false positives (5 total, dismissed on GitHub, each with an
individual justification and a passing behavioral test):
- #38/#100: _start_metadata_apply_transaction() (two call sites) raises
  ValueError only with fixed, deliberate messages.
- #44: delete_import_review_folder()'s ValueError branch, verified
  against _delete_review_source_folder()'s raise sites.
- #61/#62: _folder_clean_root() (two call sites) raises RuntimeError
  only with fixed messages; its one path-embedding variant was
  tightened to a fixed string this wave.

Won't-fix, left open (4, job-log-only taint, no top-level response
field affected, none provider/credential-adjacent -- CLAUDE.md
explicitly requires jobs to expose raw debug detail): #43, #46, #68,
#83.

Also fixes a real test regression: tests/test_ai_auth_setup_hardening.py
ClassifyOpenAiErrorBehaviorTests used an AST-extraction harness that
didn't stub `app` (needed by the new app.logger.warning() call) and
explicitly asserted the old unsafe fallback behavior. Updated the
harness and rewrote the assertion to require the exception type name
while explicitly asserting the raw message and an embedded secret are
both absent.

* docs: update SEC-002 for app.py stack-trace-exposure wave

Records wave 3's full accounting: starting/alert counts, root-cause
groups, fixes, dismissals, the 4 accepted-by-design job-log exceptions,
the test-harness regression found and fixed, remaining 220-alert
backlog by rule/file, and the next recommended wave (app.py
py/path-injection, 184 alerts).

* fix: make FolderCleanRootFalsePositiveTests platform/CI independent

FOLDER_CLEAN_ROOTS includes /tmp by design, so the "outside allowed
roots" test's use of a bare tempfile.TemporaryDirectory() (which lives
under /tmp on Linux CI) was actually inside the allowed roots there,
even though it happened to be outside them on Windows. Pin
FOLDER_CLEAN_ROOTS to a disjoint sibling directory instead, so the
test is deterministic on every platform.

* docs: dismiss job-log alerts as won't-fix, correct SEC-002 counts

The 4 job-log carve-out alerts (#43/#46/#68/#83) plus a 5th instance
that surfaced after the delete_import_review_folder fix (#413, same
_delete_review_source_folder log sources, new alert number once the
sibling "error" field became a static string) are dismissed
individually on GitHub as won't-fix with the CLAUDE.md raw-debug-detail
justification, rather than left open -- the hard zero-open-PR-alerts
merge gate requires a real disposition. Corrects the dismissal count
(10, not 5) and remaining backlog (225, not 220).
Iranman added 16 commits July 30, 2026 18:46
… end-to-end

The engine-side /audio/acoustid-lookup adapter (ARCH-012) read
ACOUSTID_API_KEY from its own process environment, but both compose files
only passed it to beets-web-manager -- the engine container never received
it, so the adapter would always report unavailable in a real deployment
regardless of configuration. Added it to the beets service in both
docker-compose.yml and docker-compose.arrs.yml, and corrected the /status
capability diagnostic (it required pyacoustid+chroma, which this fpcalc/
HTTP-based adapter never touches, instead of the key it actually needs).

Fixed several places where the adapter's distinct status values (matched/
no_match/unavailable/invalid_media/timeout/provider_error/analysis_error)
collapsed into a bare [] indistinguishable from a genuine no-match:
- helpers_mb._acoustid_lookup's except-block called logging.warning() with
  no `logging` import, so a control-agent hiccup raised an unhandled
  NameError instead of degrading gracefully (reproduced and fixed).
- Added _acoustid_lookup_status()/_acoustid_capability_available() so
  callers can tell "not attempted" apart from "attempted, no match"; wired
  the AI-evidence "acoustid_available" field to the latter instead of
  bool(candidates).
- _acoustid_lookup_cached's disk cache (never expires) persisted every
  outcome including transient failures; now only matched/no_match/
  invalid_media are cached, so a temporary engine outage can't freeze in
  as a permanent false no-match for a file.
- BeetsClient.acoustid_lookup() called the shared _request(), which
  collapses any >=500 response into a generic BeetsUnavailableError,
  discarding the engine's real {"status": "unavailable"} body -- found via
  live two-container testing, not unit tests (which had mocked _request
  directly and never exercised this path). Rewritten to preserve the
  structured body from a non-2xx response while still raising for 401 and
  genuine transport failures.

Also refactored is_safe_path() into resolve_safe_path(), which returns the
canonical (symlink-resolved) path instead of a bare bool, and updated every
direct filesystem sink (tags/read, tags/write, files/move, files/delete,
files/mkdir, acoustid-lookup, album deletion) to operate on that resolved
path instead of re-using the original caller-supplied string, closing the
gap where a sink revalidated nothing between the safety check and the
actual filesystem call.

Verified live: valid/invalid/missing auth, missing-key -> unavailable (not
no_match), path traversal/outside-root/nonexistent/directory/null-byte/
symlink-escape rejection, and the full BeetsClient -> helpers_mb chain,
against a disposable two-container engine+client stack (no production
paths, no real AcoustID network calls).
yt-dlp==2024.11.4 predates the yt_dlp.extractor.youtube.pot package that
bgutil-ytdlp-pot-provider imports at plugin-discovery time. yt-dlp's plugin
loader swallows the resulting ModuleNotFoundError and continues, so the
PO-token provider silently never registered on every yt-dlp invocation --
only a console warning was ever printed, easy to miss in job logs.

Bumped to yt-dlp==2025.6.9, the earliest version verified here to both
import the provider and actually register it (BgUtilHTTP appears in
yt_dlp.extractor.youtube.pot._registry._pot_providers). Added a real
import/registration regression test instead of relying on yt-dlp to keep
swallowing the failure quietly.
This check has never had real CI coverage: .github/workflows/unit-tests.yml
(the job that runs this test suite) never builds any web-manager image --
only docker-build.yml does, tagged beets-web-manager:ci, in a separate job
that doesn't run these tests. The test hardcoded beets-web-manager:0.1.0,
a tag nothing in CI ever produces, so it silently skipped (or, before the
prior commit's guard, coincidentally "passed" against a nonexistent image)
on every CI run.

Checking both beets-web-manager:0.1.0 and beets-web-manager:ci doesn't
close the CI coverage gap by itself, but it means the check is at least
capable of running for real in whichever environment has either image
built, instead of being permanently tied to a tag that never exists in the
job that runs it. Documented the gap in the test's docstring so it isn't
mistaken for real CI coverage.
…view

Independent final review of PR #46 found and fixed:

- backend/beets_control_agent.py had TWO conflicting definitions of
  is_safe_path(): a broken one (line ~488, calling
  resolve_safe_path(...) is not None -- which never returns None,
  since resolve_safe_path() raises UnsafePathError instead, so this
  would crash rather than return False for unsafe input) left over
  from commit 0b11a26's rebase reconciliation, silently shadowed by
  the correct try/except version already present on the rebased base
  (refactor/external-beets-engine @ 125752d). Removed the broken,
  dead first definition and its accompanying dead
  `except Exception: return None` duplicate clause in
  _path_is_within(), which was unreachable but violated that
  function's own bool return contract.
- Two duplicate bare `return` statements (tag-write's "Unsupported tag
  field" branch, and the new /audio/acoustid-lookup endpoint's
  "Path is not a regular file" branch) -- both dead code from the same
  rebase, removed.
- /audio/acoustid-lookup echoed the raw request path (`file_path`) into
  403/404/400 error bodies and a raw Python exception string into a
  500 body, inconsistent with every other sink in this file. Changed
  to the same generic-message pattern already used by
  /files/delete, /files/move, /commands/execute, etc.
- Removed the non-functional `# codeql[py/path-injection] -- sanitized
  by resolve_safe_path() above` comments: GitHub Code Scanning has no
  inline source-comment suppression mechanism, so these had zero
  actual effect and misrepresented the alert as handled. A live
  temporary CodeQL-validation PR against main (closed without merging)
  confirmed 2 open py/path-injection alerts remain on these exact
  lines for this exact head. Documented as ARCH-014 in
  docs/TECHNICAL_DEBT.md with the real remediation options (advanced
  model pack, blocked while Default Setup is enabled; or an
  owner-reviewed dismissal).
- Escalated ARCH-007 with a confirmed, more severe finding unrelated
  to this PR's own diff: backend/beets_client.py's raw_sqlite_query()
  unconditionally raises (already true on the rebased base, not
  introduced here), so every one of the ~18 remaining
  _db()/con.execute(...) call sites in app.py always raises or is
  silently swallowed by a broad except. Flagged P0 for separate
  follow-up; out of scope for this PR to fix.

All new tests pass; full suite unaffected (1553 tests, OK, skipped=6
locally / 61 on Linux CI -- unchanged).
Iranman added 4 commits July 30, 2026 18:47
Codex's independent review of the frozen head found a P1 crash: the
control agent's /audio/acoustid-lookup endpoint uses
urllib.request.Request/urlopen and urllib.error.HTTPError, but the file
only imported urllib.parse. Importing a submodule of a package does not
make sibling submodules accessible as attributes of the shared package
object unless something else in the process happens to have imported
them first -- so this worked by accident in the full test suite (another
test module's own `import urllib.request` had already bound that
attribute onto the process-wide `urllib` module) but crashed with
`AttributeError: module 'urllib' has no attribute 'request'` in any
genuinely isolated process, including the real container: a valid file
passed authentication, path validation, and fingerprinting, then crashed
the instant a configured ACOUSTID_API_KEY reached the actual provider
request.

Added the missing `import urllib.error` / `import urllib.request`
(matching the pattern already used in helpers_mb.py), and restructured
the request's exception handling to separate three previously-conflated
outcomes: HTTPError and URLError both now map to `provider_error`
immediately (no pointless retry against a rejection or connection
failure); a genuine TimeoutError still retries once before reporting
`timeout`; anything else (including a malformed JSON payload) reports
`analysis_error`. No response includes the raw HTTP status detail,
provider text, or path.

Regression coverage: AcoustidLookupProviderRouteTests exercises the real
handler through mocked urllib.request.urlopen only (matched, no_match,
HTTPError, URLError, timeout-after-retry, malformed payload), and
AcoustidLookupIsolatedImportTests spawns a genuinely fresh subprocess
that imports only backend.beets_control_agent -- the one check immune to
cross-test-module import contamination, verified to fail with the exact
reported AttributeError against the unfixed file and pass against the
fix.
Codex found helpers_mb._acoustid_lookup_status()'s except-block logging
the complete media path and the raw exception string on every
control-agent/network failure -- the exception text can itself embed a
provider URL, response body, or query parameters (e.g. an API key in a
failed request URL). The returned dict also echoed the same raw
exception text back to the caller via an "error" field.

Replaced both with a stable 12-character hash of the path (sufficient to
correlate repeated failures on the same file across log lines without
revealing its content) and the exception's type name only. Removed the
"error" field from the returned dict entirely -- no caller reads it
(confirmed by grep), so nothing downstream relied on it echoing raw text.

Regression test: captures real log output via assertLogs with a
distinctive full path and a sensitive exception message (embedding a
fake internal hostname and token), asserts the complete path, the artist
name, the raw exception text, and its embedded secret are all absent from
the log, while the exception's class name remains. Verified to fail
against the prior implementation and pass against the fix.
Codex found several stale/inaccurate AcoustID statements:

- README.md's "Required vs. optional integrations" table claimed
  AcoustID "falls back to a shared, rate-limited test key" -- directly
  contradicting the correct statement three lines above it in the same
  file (ACOUSTID_API_KEY's own description, which has always said the
  engine fails closed to "unavailable" with no fallback). No such shared
  key exists anywhere in this codebase. Corrected to match reality.
- Four AcoustID-adjacent comments/references (README.md,
  docs/CONFIGURATION.md, backend/beets_control_agent.py,
  tests/test_audio_identity_pipeline.py) pointed to ARCH-012 (a disk-walk
  library gap, unrelated to AcoustID) instead of ARCH-013 (the actual
  AcoustID capability-gap entry). Corrected all four.
- docs/TECHNICAL_DEBT.md's ARCH-014 entry still said alerts #404 and #405
  "remain open and unaddressed," describing a state that predates this
  same review cycle -- both were already independently re-verified and
  dismissed as false positives with project-owner authorization.
  Corrected to describe the dismissal, its evidence basis, and that
  GitHub's alert-fingerprinting carries it forward automatically across
  rebases, without claiming the underlying CodeQL modeling gap itself is
  resolved.

No claim is made that the broader SEC-002 backlog, ARCH-007, or the
overall application is resolved or fully healthy.
@Iranman
Iranman force-pushed the fix/plugin-first-runtime-and-codeql branch from f1b44ea to 66c2566 Compare July 31, 2026 02:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants