Skip to content

Sonar Vulnerability Disposition

This document records the reviewed disposition for the legacy SonarQube Cloud vulnerability backlog tracked by issue #755. It is intentionally narrow: new Sonar findings still fail the normal quality/security gates and this document is not a rule exclusion, NOSONAR mechanism, or Quality Gate override.

Reviewed baseline

The public SonarQube Cloud issues API on main@601d1831c6e5991805021147128c2c969f6fd7ce reported 32 open/confirmed vulnerability findings before the transport hardening change:

  • 25 pythonsecurity:S8707 path-construction findings;
  • 7 python:S5332 HTTP findings.

The HTTP family includes a real security boundary: a bearer token protects authentication but does not provide confidentiality when the server is bound to a non-loopback interface over cleartext HTTP. The implementation therefore fails closed unless a non-loopback deployment has either direct TLS certificate termination or a declared protected public endpoint. Remote public endpoints must be HTTPS; loopback HTTP is allowed for local/container port publishing. The dashboard CLI is loopback-only.

S8707 disposition

The 25 remaining S8707 flows are not path components derived from model, provider, artifact, or request content. They are explicit filesystem roots/files selected by repository CLI entry points. Those commands are intentionally able to evaluate caller-selected fixtures, policies, manifests, evidence directories, and output files outside the repository checkout. Confining them mechanically to REPO_ROOT or the current working directory would break supported CLI behavior without creating a real trust boundary.

Where an untrusted child path exists inside a selected root, it is handled by a separate boundary. For example, release checksum artifact identities are validated as basenames before joining them to the selected artifact directory (the traversal fix delivered in #789). The rows below therefore accept only the specific top-level caller-selected path flow that Sonar reports; they do not accept traversal inside those roots.

Sonar location Flow source Disposition Technical evidence
scripts/check_live_model_release_policy.py:59 --github-output Accepted CLI contract GitHub Actions explicitly supplies the output file path; model/provider data is never used as a path component.
scripts/generate_gui_release_evidence.py:26 --cargo-lock Accepted CLI contract Standalone packaging verifier intentionally reads a caller-selected Cargo lockfile.
scripts/generate_gui_release_evidence.py:67 --cargo-lock Accepted CLI contract Same explicit lockfile root; dependency parsing does not derive a new filesystem path from package data.
scripts/generate_gui_release_evidence.py:188 --output-dir Accepted CLI contract The caller intentionally selects the release-evidence output directory.
scripts/generate_gui_release_evidence.py:218 --evidence Accepted CLI contract Verification intentionally accepts a caller-selected evidence file. Evidence-declared artifact names are separately inventory-constrained before hashing.
scripts/generate_release_evidence.py:44 --pyproject Accepted CLI contract The CLI supports verification of an explicitly selected Python project file.
scripts/generate_release_evidence.py:133 --checksums Accepted CLI contract The checksum manifest itself is caller-selected; entries inside it are independently basename-validated before artifact access.
scripts/generate_release_evidence.py:166 --output Accepted CLI contract The output evidence path is explicitly selected by the caller.
scripts/kicad11_headless_canary.py:221 --artifacts Accepted CLI contract Canary artifacts are intentionally written below the caller-selected artifacts root.
scripts/kicad_canary.py:748 analyzer flow from run CLI parse Analyzer limitation The sink is _resolve_cli() over fixed environment/PATH candidates and does not receive the parsed --artifacts/--kicad-range values reported as the source. Candidates are required to exist and be files before resolution.
scripts/kicad_canary.py:912 --artifacts Accepted CLI contract The run command intentionally owns a caller-selected artifact directory.
scripts/validate_mcp_manifest.py:317 positional manifest / registry publisher manifest Accepted CLI contract Validating an arbitrary supplied manifest is the command's documented purpose; registry publication separately validates repository metadata and package contracts.
src/kicad_mcp/evals/baseline_promotion.py:65 --aggregate-report Accepted CLI contract Baseline generation intentionally consumes a caller-selected aggregate evidence file.
src/kicad_mcp/evals/baseline_promotion.py:71 --baseline-template Accepted CLI contract Promotion can operate on an explicitly selected baseline template; no model value becomes a path component.
src/kicad_mcp/evals/baseline_promotion.py:164 --aggregate-report Accepted CLI contract Same caller-selected aggregate evidence boundary during promotion validation.
src/kicad_mcp/evals/baseline_promotion.py:179 --output Accepted CLI contract The protected workflow deliberately selects the candidate-baseline output path.
src/kicad_mcp/evals/live_runner.py:422 --output Accepted CLI contract Evaluation output location is a caller-selected evidence destination; model output is serialized as content, not a path.
src/kicad_mcp/evals/nvidia_nim_adapter.py:299 --tools-reference Accepted CLI contract Adapter/eval CLIs intentionally support an explicit tools-reference fixture; model/provider content does not select the file.
src/kicad_mcp/evals/release_gate.py:31 --baseline Accepted CLI contract Release evaluation accepts an explicitly selected reviewed baseline file.
src/kicad_mcp/evals/release_gate.py:393 --output Accepted CLI contract The gate writes its report to the caller-selected protected evidence path.
src/kicad_mcp/evals/release_policy.py:116 --policy Accepted CLI contract Policy classification deliberately supports an explicitly supplied policy file for local/protected evaluation.
src/kicad_mcp/evals/release_policy.py:146 --baseline Accepted CLI contract The approved baseline file is a caller/workflow-selected input, not a path derived from untrusted evidence.
src/kicad_mcp/evals/smoke_assurance.py:285 --output Accepted CLI contract Smoke assurance writes one sanitized report to the explicitly selected output path.
src/kicad_mcp/evals/tool_selection.py:255 --cases Accepted CLI contract Eval runners intentionally load a caller-selected benchmark case file.
src/kicad_mcp/evals/tool_selection.py:616 --thresholds Accepted CLI contract Eval/release runners intentionally load a caller-selected threshold contract.

S5332 disposition

The seven pre-hardening python:S5332 findings were reviewed individually. The real risk was not the existence of local HTTP itself; it was that a non-loopback bind could previously carry a bearer token over plaintext. The transport policy now removes that state from the supported configuration space.

Pre-hardening location Disposition after this change Technical evidence
src/kicad_mcp/config.py URL-scheme text Analyzer-only/message construction The wording no longer embeds an http:// transport example and does not initiate network I/O.
src/kicad_mcp/server.py auth/resource base URL (two flows) Remediated Auth metadata uses advertised_http_base_url; non-loopback direct mode requires TLS, tls-proxy requires HTTPS, and loopback-proxy permits HTTP only on a loopback public origin.
src/kicad_mcp/server_info.py advertised endpoint (two flows) Remediated Discovery uses the same validated advertised base URL; remote plaintext advertisement is not a valid configuration.
src/kicad_mcp/server.py dashboard/browser URLs (two flows) Accepted loopback-only HTTP dashboard --host now rejects non-loopback hosts. Local dashboard HTTP remains intentionally supported and never represents a remote cleartext transport.

This is an intentional security compatibility break for deployments that directly bound remote plaintext HTTP with only a bearer token. Migration is explicit: terminate TLS directly with KICAD_MCP_TLS_CERT_FILE and KICAD_MCP_TLS_KEY_FILE, use tls-proxy with an HTTPS public base URL, or use loopback-proxy only when a bind-all container port is published to host loopback.

Review rules

These dispositions are valid only while the corresponding data flow remains a top-level caller-selected CLI path. Re-review is required if any of the following changes occur:

  • model/provider/request/artifact content becomes part of a filesystem path;
  • a selected root begins accepting unvalidated child names;
  • one of these CLIs is exposed as a remote request handler with a filesystem sandbox promise;
  • a workflow begins passing untrusted event text into one of these path options.

No Sonar rule, severity, exclusion, Quality Gate, CodeQL query, or coverage threshold is changed by this disposition.