Skip to content

Authenticate public inference endpoints - #124

Merged
volen-silo merged 11 commits into
mainfrom
fix/authenticate-public-endpoints
Jul 22, 2026
Merged

Authenticate public inference endpoints#124
volen-silo merged 11 commits into
mainfrom
fix/authenticate-public-endpoints

Conversation

@volen-silo

Copy link
Copy Markdown
Collaborator

Summary

rocm serve no longer launches an anonymous, network-reachable server when bound to a public (non-loopback) interface. Public binds now require an API key; loopback binds stay credential-free (unchanged default).

  • Key policy: on a public bind the key comes from --api-key / ROCM_SERVE_API_KEY, or is generated (48-char CSPRNG). An empty supplied key is rejected.
  • Engine enforcement (off-argv): vLLM VLLM_API_KEY, Lemonade server LEMONADE_API_KEY, packaged llama-server fallback --api-key-file.
  • CLI self-clients: readiness probe, startup smoke test, and local rocm chat send Authorization: Bearer so they keep working against the protected endpoint.
  • Secure delivery + redaction: the key is printed once as client configuration and never appears in rocm services, rocm logs, or the audit log.
  • Persistence: a 0600 per-service key file (not the OS keychain — headless serving hosts routinely lack a Secret Service/D-Bus session); cleared on stop.
  • Fail closed: Windows managed Lemonade cannot enforce the key through the platform's path-only spawn primitive, so public bind + --engine lemonade + Windows is refused with guidance to use vLLM or a loopback host.

Testing

  • New unit tests across rocm, rocm-core, and rocm-engine-protocol (key policy, generator, key-file reader, client-config rendering, redaction, fail-closed guard).
  • Full workspace test suites pass; cargo clippy -D warnings clean.
  • Enforcement of unauthenticated-request rejection is an engine-side behavior verified against a live GPU engine via the acceptance scripts (deferred to a GPU host), as noted in docs/testing.md.

Follow-up

  • Teaching the Windows detached-spawn primitive to carry a value-typed env override (so Windows Lemonade can serve public with auth) is left as a separate task; today that combination fails closed.

Comment thread crates/rocm-engine-protocol/src/lib.rs Fixed
Comment thread crates/rocm-engine-protocol/src/lib.rs Dismissed
Comment thread crates/rocm-engine-protocol/src/lib.rs Dismissed
Comment thread crates/rocm-engine-protocol/src/lib.rs Dismissed
Comment thread crates/rocm-engine-protocol/src/lib.rs Dismissed
Comment thread crates/rocm-engine-protocol/src/lib.rs Dismissed
Comment thread crates/rocm-engine-protocol/src/lib.rs Fixed
@michaelroy-amd

Copy link
Copy Markdown
Member

Review findings against head 6a66ea4:

P1: restart drops authentication

restart_internal_managed_service calls stop_internal_managed_service at apps/rocm/src/main.rs:12390. The stop path deletes the endpoint key file at line 12346, but restart only tries to read that file afterward at lines 12427-12428. A previously authenticated public service therefore restarts without the auth carrier on its original public host.

Preserve/read the key before stopping, or split restart-safe process termination from terminal key cleanup. Add a regression test proving a public service retains the same key across restart and removes it only on a final stop.

P1: protected Lemonade fallback fails its own probes

The direct llama-server fallback enables --api-key-file at engines/lemonade/src/lib.rs:1858-1870, but wait_for_openai_models_ready uses an unauthenticated request at line 2216 and query_chat_smoke_endpoint emits no Authorization header at lines 2346-2350. A correctly protected fallback returns 401, is treated as failed, and is killed.

Thread the resolved key into both probes and send Authorization: Bearer <key>. Cover the authenticated readiness and smoke paths with a regression test.

P1: local chat cannot discover protected services

ready_local_services calls managed_service_endpoint_model_ready at apps/rocm/src/providers.rs:529 before loading the service key. The core helper performs an anonymous /v1/models request, so authenticated public services are filtered out before LocalProvider::chat can send the bearer header added by this PR.

Make service discovery readiness key-aware, or select the manifest first and perform an authenticated probe. Add a protected-service local-chat discovery test.

Current checks

  • prek (lint / hygiene) is failing because cargo fmt --all --check would reformat main.rs and providers.rs.
  • The CodeQL required check is failing with the existing path-expression alerts and still needs disposition.
  • Affected-crate, workspace, and Windows build/test jobs were skipped after the hygiene failure.
  • docs/testing.md:437 says the key is stored in the OS keychain, while this implementation deliberately uses a 0600 file.

Verdict: not ready. Fix order: preserve restart authentication, authenticate Lemonade fallback probes, authenticate local service discovery, add regressions, then clear required checks.

@volen-silo

Copy link
Copy Markdown
Collaborator Author

Thanks for the thorough review — all three P1s were real. Fixed in 84edba5:

P1 restart drops authenticationrestart_internal_managed_service now captures the key before stop_internal_managed_service deletes it and re-stores it before respawn, so a public service returns on the same host with the same key. Terminal key cleanup still happens only on a real stop.

P1 protected engine probes fail themselves — the readiness/smoke probes were anonymous. Made the shared path auth-aware (http_get_text_with_auth, openai_models_endpoint_has_model, managed_service_endpoint_model_ready now take Option<&str>) and threaded the resolved key into the Lemonade fallback readiness + chat-smoke probes. While there, the same class existed in the vLLM healthcheck (query_loaded_model_endpoint) — also fixed.

P1 local chat cannot discover protected servicesready_local_services and the managed-service liveness refresh now probe with the service's key, so authenticated public services survive discovery and reach LocalProvider's bearer path.

Regression test — added openai_models_endpoint_sends_bearer_when_key_present in rocm-core: a mock endpoint that 401s without the key and lists the model with it, asserting not-ready without the key and ready with it. This covers the readiness-probe class shared by the Lemonade fallback, vLLM, and local discovery.

Hygiene

  • prekcargo fmt --all --check is now clean.
  • docs/testing.md — corrected: the key is a 0600 per-service file, not the OS keychain.
  • CodeQL — the two open rust/path-injection alerts are pre-existing on main (rocm-dash-collectors/src/bench_tail.rs, rocm-dash-daemon/src/demo.rs); this branch introduces no new alerts (code-scanning/alerts?ref=…/fix/authenticate-public-endpoints returns none). Flagging for disposition since neither file is touched here.

Full workspace tests pass and cargo clippy -D warnings is clean.

@rominf rominf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review: requesting changes.

The overall authentication design is sound, but one secret-lifecycle defect should be fixed before merge: engines/lemonade/src/lib.rs:1863 writes .llama-server-api-key for the packaged llama-server path, but no stop path removes that file. The sibling <service_id>.endpoint-key is cleared on stop, and the endpoint-key module documents that lifecycle. As written, a stopped or rotated endpoint leaves the old plaintext key on disk indefinitely. Please remove the llama-server key copy during service teardown and add a regression test for cleanup.

Additional coverage strongly recommended for this security boundary:

  • Assert that VLLM_API_KEY, LEMONADE_API_KEY, and --api-key-file reach the spawned engine commands.
  • Verify the key is absent from service, log, and audit output.
  • Verify the private key files have mode 0600 on Unix.

The CodeQL path-injection alerts appear false-positive: the production path comes from ROCM_SERVE_API_KEY_FILE, which the CLI sets only for its own child process from the service directory; the other alerts are test temporary paths. These still need dismissal or narrow suppression so the gate is green.

The branch also conflicts with current main in apps/rocm/src/main.rs and engines/lemonade/src/lib.rs; please rebase before merge.

Minor accuracy notes: one comment says “keychain” although this feature reads a private file, and docs/testing.md defers live auth rejection to GPU scripts that currently contain no authentication check.

Public (non-loopback) `rocm serve` binds now require an API key instead of
launching an anonymous, network-reachable server. Loopback stays
credential-free (unchanged default).

The key is taken from --api-key / ROCM_SERVE_API_KEY or generated, then handed
to the engine off-argv: vLLM via VLLM_API_KEY, Lemonade via LEMONADE_API_KEY,
and the packaged llama-server fallback via --api-key-file. The CLI's own
readiness probe, smoke test, and local chat send it as a bearer token so they
keep working against the now-protected endpoint. The key is printed once as
client configuration and never appears in logs, `services`, or audit output.

Persistence is a 0600 per-service file rather than the OS keychain: public
serving is a headless-server action and those hosts routinely lack a Secret
Service/D-Bus session. Windows managed Lemonade cannot enforce the key through
the platform's path-only spawn primitive, so that combination fails closed.

Signed-off-by: Eugene Volen <Eugene.Volen@amd.com>
Address review of the public-endpoint auth change:

- Restart preserved authentication: `restart_internal_managed_service` read the
  key file only after `stop` had deleted it, so a public service came back
  unauthenticated. Capture the key before stop and re-store it before respawn.
- Protected engine probes no longer self-reject: the CLI's readiness and smoke
  probes issued anonymous requests, so a correctly protected server answered 401
  and was treated as failed/killed. Thread the key through the shared
  `http_get_text_with_auth` / `openai_models_endpoint_has_model` /
  `managed_service_endpoint_model_ready` helpers and the Lemonade fallback
  readiness + chat-smoke probes and the vLLM healthcheck.
- Local service discovery is key-aware: `ready_local_services` and the
  managed-service liveness refresh now probe with the service key, so protected
  public services are discoverable by `rocm chat` instead of being filtered out.
- docs/testing.md no longer claims keychain storage (it is a 0600 file).

Adds a rocm-core regression test proving the readiness probe sends the bearer
token (401 without the key, ready with it). Formatting brought to `cargo fmt`.

Signed-off-by: Eugene Volen <Eugene.Volen@amd.com>
The packaged llama-server fallback wrote a second copy of the endpoint
API key (.llama-server-api-key) to pass via --api-key-file, but no stop
path removed it — leaving a plaintext key on disk after a service stopped
or its key rotated. Point --api-key-file at the existing CLI-managed 0600
key file (ROCM_SERVE_API_KEY_FILE) instead of copying the secret, so the
key's lifecycle stays owned by `rocm serve` (created before spawn, deleted
on stop) with no stale copy left behind.

Add resolve_endpoint_api_key_file() with an env-free, testable gate, and
cover key-file resolution, the 0600 mode, and store/clear cleanup.

Signed-off-by: Eugene Volen <Eugene.Volen@amd.com>
The smoke-test comment said the local provider reads the endpoint key
from the OS keychain; it reads the per-service 0600 key file. And
docs/testing.md claimed the GPU acceptance script asserts rejection of
unauthenticated requests, but that script has no such check — mark it a
deferred follow-up and list the coverage the unit tests actually provide.

Signed-off-by: Eugene Volen <Eugene.Volen@amd.com>
@volen-silo
volen-silo force-pushed the fix/authenticate-public-endpoints branch from 84edba5 to cb6e931 Compare July 20, 2026 08:22
Comment thread apps/rocm/src/endpoint_keys.rs Dismissed
Comment thread apps/rocm/src/endpoint_keys.rs Dismissed
Comment thread apps/rocm/src/endpoint_keys.rs Dismissed
Comment thread apps/rocm/src/endpoint_keys.rs Dismissed
Comment thread apps/rocm/src/endpoint_keys.rs Dismissed
Comment thread apps/rocm/src/main.rs Dismissed
Comment thread crates/rocm-engine-protocol/src/lib.rs Dismissed
Comment thread crates/rocm-engine-protocol/src/lib.rs Dismissed
Comment thread crates/rocm-engine-protocol/src/lib.rs Dismissed
Comment thread crates/rocm-engine-protocol/src/lib.rs Dismissed
@volen-silo
volen-silo requested a review from rominf July 20, 2026 08:38
@volen-silo

Copy link
Copy Markdown
Collaborator Author

CodeQL rust/path-injection alerts — disposition

The CodeQL gate flags 19 rust/path-injection alerts on the current head (cb6e931). All are false positives — none represent attacker-controllable path flow. Documenting here for disposition (the count grew from the original 7 because the new security-boundary tests and the resolve_endpoint_api_key_file helper touch more paths).

Production code (4) — false positive:

Alert Location Flagged op Why it's safe
#702 crates/rocm-engine-protocol/src/lib.rs:73 read_to_string in endpoint_api_key_from_file Path is ROCM_SERVE_API_KEY_FILE, which the CLI sets only on its own __engine-serve-http child, pointing at a 0600 file under the app services dir. Never external input.
#718 apps/rocm/src/endpoint_keys.rs:56 remove_file in clear_endpoint_api_key Path = services_dir/{service_id}.endpoint-key.
#724 / #729 apps/rocm/src/main.rs:4385,4395 create_dir_all / open in write_private_file_0600 Same service_id-derived path under the services dir.

service_id cannot traverse the services dir: it is either produced by generate_service_id (sanitize_component(engine) + a sanitized 24-char model slug + unix_time_millis), or — on the user-facing stop / restart --service-id paths — passed through validate_service_id, which rejects / and \.

Test code (15) — used in tests: unit-test temp-dir paths in the rocm-engine-protocol and apps/rocm/endpoint_keys test modules (#703–708, #719–723, #725–728). Not production surface.

Durable follow-up: this query re-fires on any test that touches a path, so per-alert dismissal is a treadmill. A paths-ignore for test modules in the CodeQL config would stop the recurring test-path noise, but that's a repo-wide change and out of scope for this PR.

@rominf rominf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The authentication flow is substantially improved, but the supplied API key still crosses a raw HTTP-header boundary without rejecting embedded CR/LF. Please reject those characters at input validation and add a regression test before merge.

Comment thread apps/rocm/src/main.rs
}
match supplied {
Some(raw) => {
let trimmed = raw.trim();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trim() only removes leading/trailing whitespace; an embedded \r or \n remains in the accepted key. That key is later interpolated directly into raw Authorization headers in the core probe, the local-service probe, and Lemonade's smoke request. For example, a supplied value containing key\r\nX-Injected: value becomes an additional header on those requests. Please reject CR/LF here (and defensively when reading the key file, if appropriate) and add a regression test covering an embedded newline.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in 72efed0.

Root cause confirmed: the key is interpolated verbatim into raw Authorization: Bearer {key}\r\n header lines (core probe, local-service probe, and Lemonade's smoke request), and trim() only strips the ends, so an embedded CR/LF survived and injected an extra header line.

Fix:

  • New shared predicate rocm_core::endpoint_api_key_has_forbidden_chars rejects any control character (covers CR/LF and the whole class).
  • resolve_endpoint_auth now rejects a supplied --api-key / ROCM_SERVE_API_KEY containing a control char at input validation, with a clear error.
  • endpoint_api_key_from_file defensively returns None for a key file holding control chars — defense in depth behind the input validation.

Regression tests at all three sites:

  • resolve_endpoint_auth_public_rejects_embedded_crlf (covers good-key\r\nX-Injected: value, bare \n, bare \r)
  • endpoint_api_key_from_file_rejects_embedded_control_chars
  • endpoint_api_key_has_forbidden_chars_flags_control_chars (plus asserting a generated key passes)

cargo clippy -D warnings and cargo fmt --all --check are clean.

The endpoint API key is interpolated verbatim into raw
`Authorization: Bearer {key}\r\n` header lines by the core, local, and
Lemonade probes. `trim()` only strips surrounding whitespace, so a
supplied key carrying an embedded CR/LF (e.g. `key\r\nX-Injected: value`)
survived validation and injected an extra header line on those requests.

Reject any control character at input validation in resolve_endpoint_auth,
and defensively when reading the key file in endpoint_api_key_from_file,
via a shared endpoint_api_key_has_forbidden_chars predicate in rocm-core.
Add regression tests covering embedded newlines at all three sites.

Signed-off-by: Eugene Volen <Eugene.Volen@amd.com>
@volen-silo
volen-silo requested a review from rominf July 20, 2026 09:33
Comment thread crates/rocm-engine-protocol/src/lib.rs Dismissed
Comment thread crates/rocm-engine-protocol/src/lib.rs Dismissed
Comment thread crates/rocm-engine-protocol/src/lib.rs Dismissed
Comment thread crates/rocm-engine-protocol/src/lib.rs Dismissed

@rominf rominf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two issues should be addressed before merge:

  1. Daemon recovery can restart a public endpoint without authentication. The initial rocm serve path persists the endpoint key, but restart_managed_service launches rocmd supervise using recovery_supervise_args, which carries no endpoint-key information. supervise_service then starts the engine without ROCM_SERVE_API_KEY_FILE. With automation enabled, a managed public service that crashes or enters a recoverable state can therefore be restarted anonymously. The daemon health-check subprocess also receives no endpoint key, so it may classify the protected endpoint as unhealthy and trigger this path. Please propagate the service key into health checks and recovery, fail closed when a public service's key is unavailable, and add a regression test proving recovery preserves authentication.

  2. The attached AlreadyRunning path leaves an orphan endpoint-key file. serve() stores the newly generated key before spawning. The background path clears that unused key when an equivalent service already exists, but run_attached_service returns from ManagedSpawn::AlreadyRunning without doing so. Repeated attached public-bind invocations therefore accumulate unused *.endpoint-key files. Please mirror the background cleanup before returning.

I verified the relevant unit tests and cargo clippy --workspace --all-targets -- -D warnings; those passed. The aggregate CodeQL check is currently red even though its individual language-analysis jobs are green, so that check also needs triage before merge.

@volen-silo volen-silo left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: posted as a COMMENT review because GitHub does not permit the PR author to formally REQUEST_CHANGES on their own PR. Intended verdict: REQUEST_CHANGES — the confirmed blockers below must be resolved before merge.

Follow-up review (review-only). I independently re-verified the two blocking concerns from the previous review (#124 (review)) against the current PR head 72efed0, plus their blast radius. No new commits have landed since that review, so both concerns remain open, and the blast-radius pass surfaced two further instances of the same root gap. Requesting changes.

Blocking (confirmed against source)

1. Daemon recovery restarts a public endpoint unauthenticated — CONFIRMED, still open.
The engine obtains the key only from the ROCM_SERVE_API_KEY_FILE env var (crates/rocm-engine-protocol/src/lib.rs:38-56; no service-id-based fallback). The initial spawn sets it (apps/rocm/src/main.rs:4603-4626) and the CLI-side rocm services restart re-threads it (apps/rocm/src/main.rs:12781-12784, 12844-12846). But the daemon's recovery path is a separate function that never does: restart_managed_servicerecovery_supervise_argssupervise_serviceengine_serve_http_args (apps/rocmd/src/lib.rs:4751, 4796, 3038, 3148) spawn rocm __engine-serve-http with no .env(ENDPOINT_API_KEY_FILE_ENV, …) anywhere. apps/rocmd has zero references to the (crate-private) endpoint_keys module, and ManagedServiceRecord carries no key-file field, so the daemon has no way to re-thread it as written. A Contained-mode server-recover restart therefore brings a previously-protected public service back up anonymous. Reachable via automatic recovery and the agent-exposed restart_server sandbox tool (apps/rocmd/src/lib.rs:593-599).
Fix: persist the key-file path on ManagedServiceRecord (or resolve it deterministically in a shared crate) and set ROCM_SERVE_API_KEY_FILE on the re-spawned child in supervise_service; fail closed when a public service's key is unavailable; add a regression test proving recovery preserves auth.

2. Attached AlreadyRunning leaves an orphan endpoint-key file — CONFIRMED, still open.
serve() stores the fresh key before choosing background vs attached (apps/rocm/src/main.rs:4187-4189). The background path clears it on already_running (4215-4217), but run_attached_service's ManagedSpawn::AlreadyRunning arm returns without clearing (4894-4902). Repeated attached public-bind invocations against an already-running equivalent service accumulate unused *.endpoint-key files.
Fix: mirror the background cleanup in that arm (clear the fresh service_id's key before returning).

3. Daemon stop_managed_service never clears the key file — CONFIRMED (blast radius of #1).
stop_managed_service (apps/rocmd/src/lib.rs:2567-2612) signals PIDs and marks the record stopped but never deletes the endpoint-key file. This backs the stop_server sandbox tool and the MCP handler — stop paths distinct from the CLI rocm services stop (which does clear). Stopping a public service via the daemon/assistant path leaves a stale plaintext key on disk.
Fix: clear the key file on this stop path too (same shared-resolution mechanism as #1).

Resolved since earlier rounds (independently confirmed)

  • llama-server key copy: no separate .llama-server-api-key is written; the managed 0600 file is reused via --api-key-file (engines/lemonade/src/lib.rs:1754-1761); repo-wide grep for the old filename is empty.
  • CR/LF / control-char injection: rejected at input (resolve_endpoint_auth, apps/rocm/src/main.rs:4338-4364) and defensively on file read (crates/rocm-engine-protocol/src/lib.rs:72-84) via endpoint_api_key_has_forbidden_chars (rejects the full control class), with regression tests at both layers; generated keys are alphanumeric-only.
  • "keychain" comment: no inaccurate keychain reference remains for this feature.
  • docs/testing.md: honestly discloses the live unauth-rejection assertion as a deferred GPU-script gap rather than overclaiming.
  • Rebase: branch no longer conflicts with main.

Non-blocking

  • apps/rocm/src/provider_keys.rs:261: with_keyring_entry doc comment implies endpoint keys will reuse the OS-keyring chokepoint, but this feature deliberately uses 0600 files (headless hosts lack Secret Service/D-Bus). Comment-rot worth correcting so a future contributor doesn't route endpoint keys through the keyring.
  • Test coverage (recommended in round 1, still partial): no test asserts VLLM_API_KEY reaches the vLLM command env or --api-key-file reaches the llama-server argv, nor an integration test that the key is absent from services/logs/audit output. Structurally safe today (ManagedServiceRecord has no key field) but not pinned by a regression test.

CI

The aggregate CodeQL check is red while Analyze (actions/python/rust) are all green — matches the prior review's triage note; still needs resolving before merge. cargo clippy --workspace --all-targets -- -D warnings is clean locally; endpoint-auth unit tests pass across rocm, rocm-core, rocm-engine-protocol, and both engines. (Two proc_lifecycle tree-stop tests fail locally, but that file is untouched by this PR — environment-sensitive, unrelated.)

@volen-silo
volen-silo requested a review from rominf July 20, 2026 15:00
Signed-off-by: Eugene Volen <Eugene.Volen@amd.com>
Comment thread crates/rocm-engine-protocol/src/lib.rs Dismissed
Comment thread crates/rocm-engine-protocol/src/lib.rs Dismissed
Comment thread crates/rocm-engine-protocol/src/lib.rs Dismissed
Comment thread crates/rocm-engine-protocol/src/lib.rs Dismissed
Comment thread crates/rocm-engine-protocol/src/lib.rs Dismissed
Comment thread crates/rocm-engine-protocol/src/lib.rs Dismissed

@rominf rominf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two secret-lifecycle issues remain at head 5a1ad28 and should be fixed before merge:

  1. Daemon stop leaves the endpoint credential on disk. stop_managed_service in apps/rocmd/src/lib.rs:2567-2612 terminates the recorded processes and marks the service stopped, but it never removes <service_id>.endpoint-key. This path backs the MCP and assistant stop_server operations, so stopping a protected public endpoint through either path leaves its plaintext bearer credential indefinitely. The CLI stop path already performs best-effort cleanup in apps/rocm/src/main.rs:12730-12733; please apply equivalent cleanup here and add a regression test for daemon-driven stop.

  2. Attached AlreadyRunning leaves an orphan endpoint-key file. serve() persists the newly generated key before spawning (apps/rocm/src/main.rs:4184-4189). The background path clears that unused key when an equivalent service is already running, but run_attached_service returns from ManagedSpawn::AlreadyRunning at apps/rocm/src/main.rs:4894-4902 without clearing it. Repeated attached public-bind invocations therefore accumulate unused credential files that are not associated with any service record and have no later cleanup path. Please mirror the background cleanup in this branch and add a regression test.

I re-ran the endpoint-auth tests and cargo clippy --workspace --all-targets -- -D warnings; both passed. The aggregate CodeQL check is still red and also needs disposition before merge.

Signed-off-by: Eugene Volen <Eugene.Volen@amd.com>
Signed-off-by: Eugene Volen <Eugene.Volen@amd.com>
@volen-silo

Copy link
Copy Markdown
Collaborator Author

On the CodeQL disposition:

This branch introduces zero new code-scanning alerts — the three analysis jobs (Analyze (python/rust/actions)) all pass on the current head, and every open alert is attributed to refs/heads/main, not this branch:

  • py/path-injection: 48 (main)
  • rust/path-injection: 45 (main)
  • py/command-line-injection: 6 (main)
  • py/full-ssrf: 1 (main)
  • this branch: 0

The red aggregate CodeQL check reflects inherited baseline debt on main, not a defect in this PR's diff. Proposed handling, on two separate tracks:

  1. Gating — switch the required code-scanning check to diff-based (fail only on alerts introduced by the PR) so PRs that add nothing new aren't blocked by baseline debt. This unblocks the endpoint-auth work without hiding anything.
  2. Baseline triage (separate) — categorize the main alerts by rule and sink, fix genuinely real ones in dedicated PRs, and dismiss false positives per-alert with a documented reason rather than a global suppression. Where a pattern is systematically flagged (e.g. a service_id joined into a path), add a validation/sanitizer barrier at the boundary so the dataflow analysis recognizes the guard — this is both a real hardening and a legitimate way to clear the FPs.

I'll keep the baseline triage out of this PR's scope so the auth fixes stay reviewable on their own.

@rominf rominf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up review at head 00f4db48: both requested production fixes are correct. Daemon-driven stop now removes the endpoint key, and the attached AlreadyRunning path removes the fresh orphan key. One regression-test issue remains before approval:

The daemon-stop test removes the entire test directory before checking the key file. In stop_managed_service_removes_endpoint_key_file (apps/rocmd/src/lib.rs:7861-7905), fs::remove_dir_all(root) runs at line 7890 before the assertion at lines 7900-7903 that key_path no longer exists. The assertion is therefore true even when the production remove_file call is removed. I confirmed this empirically: reverting the production cleanup still leaves this test passing.

Please move the key-file assertion before remove_dir_all(root) so the requested regression test fails without the fix.

Local formatting, clippy, rocmd library tests, rocm-engine-protocol tests, and the targeted endpoint-key tests pass. Separately, the aggregate CodeQL gate remains red even though its language-analysis jobs pass, and still needs disposition before merge.

stop_managed_service_removes_endpoint_key_file called remove_dir_all on
the temp root before asserting the endpoint-key file was gone, so the
assertion held even when the production remove_file was absent. The test
could not fail for the regression it was written to guard.

Capture the key file's absence from the real filesystem immediately
after stop and before the blanket cleanup, then assert on that. Verified
empirically: reverting the remove_file in stop_managed_service now fails
this test, and it passes with the fix in place.

Signed-off-by: Eugene Volen <Eugene.Volen@amd.com>
The endpoint-key file path is built as
services_dir().join("{service_id}.endpoint-key"). Three call sites
independently re-checked that a service id carried no path separator
before reaching that join, so the sanitizer was invisible across
functions (CodeQL flags the joins as path-injection).

Introduce a validated rocm_core::ServiceId newtype (rejects empty, path
separators, "..", and control characters) as the single source of
truth, route the three existing checks through it, and assert in
endpoint_key_file_path that the built path is a direct child of the
services directory so a stray id fails closed at the sink rather than
escaping the directory. No behaviour change for valid ids; adds
ServiceId unit tests.

Signed-off-by: Eugene Volen <Eugene.Volen@amd.com>
@volen-silo
volen-silo requested a review from rominf July 21, 2026 13:42
@volen-silo
volen-silo enabled auto-merge July 21, 2026 13:50
The previous commit routed the local-webhook service_id check through
ServiceId::new but wrapped it in .with_context("invalid service_id ..."),
and anyhow's top-level Display shows only the outer context. That hid the
"must not contain path separators" text that
local_webhook_event_rejects_service_id_path_separators asserts on, failing
the rocmd tests on Linux and Windows.

Propagate the ServiceId error verbatim so its own message surfaces.

Signed-off-by: Eugene Volen <Eugene.Volen@amd.com>
@volen-silo
volen-silo added this pull request to the merge queue Jul 22, 2026
Merged via the queue into main with commit 4591c3a Jul 22, 2026
16 of 19 checks passed
@volen-silo
volen-silo deleted the fix/authenticate-public-endpoints branch July 22, 2026 08:01
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.

4 participants