Skip to content

feat(dashboard-api): route logs through read-only service endpoint#808

Merged
Lightheartdevs merged 2 commits intoLight-Heart-Labs:mainfrom
yasinBursali:feat/service-logs-dashboard
Apr 6, 2026
Merged

feat(dashboard-api): route logs through read-only service endpoint#808
Lightheartdevs merged 2 commits intoLight-Heart-Labs:mainfrom
yasinBursali:feat/service-logs-dashboard

Conversation

@yasinBursali
Copy link
Copy Markdown
Contributor

What

  • Update extension_logs() to call /v1/service/logs instead of /v1/extension/logs, enabling log viewing for core services
  • Add container_name to SERVICES dict for accurate container name resolution
  • Replace bare except Exception: with specific urllib.error.HTTPError and urllib.error.URLError catches
  • Add Docker Compose label spoofing prevention in _scan_compose_content()

Why

  • Core services (llama-server, open-webui, dashboard-api) return 403 when viewing logs because validate_service_id() blocks them
  • The dashboard Terminal button already renders for core services but the fetch fails silently
  • Container name hardcoding (dream-{service_id}) fails for open-webui (container is dream-webui)
  • A malicious extension could spoof Docker Compose labels to masquerade as a core service

How

  • URL change: /v1/extension/logs/v1/service/logs (the new read-only endpoint that allows all services)
  • Error propagation: HTTPError handler forwards the actual status code from host agent (not always 503). URLError/OSError returns 503 with f-string fallback: "Host agent unavailable. Use 'docker logs dream-{service_id}' in terminal."
  • Label security: Rejects any extension compose YAML that sets com.docker.compose.* labels (handles both dict and list label formats)
  • container_name: Read from manifest, falls back to dream-{service_id}

Three Pillars Impact

  • Install Reliability: No installer changes
  • Broad Compatibility: Standard urllib, all Python versions
  • Extension Coherence: Existing /v1/extension/logs unchanged; label spoofing prevention strengthens extension security

Modified Files

  • dashboard-api/config.pycontainer_name field in SERVICES dict
  • dashboard-api/routers/extensions.pyextension_logs() rewrite + label spoofing check

Testing

Automated

  • Python compile: PASS
  • Existing tests: 173 pass (1 pre-existing failure unrelated)

Manual

  • View logs for core service (llama-server) via dashboard Terminal → should work (was 403 before)
  • View logs for user extension → still works
  • Host agent down → 503 with helpful fallback message
  • Extension with com.docker.compose.service label → rejected at install time

Review

  • CG: ⚠️ APPROVED WITH WARNINGS (D must merge first — operational dependency)
  • Security: PASS (label spoofing covers dict+list formats)

Platform Impact

  • All platforms supported. WSL2 (no host agent) gets helpful 503 fallback

Sequence

PR 5 of 7 (Phase 3). Depends on PR 4 (#806 — host agent logs endpoint)

Update extension_logs() to call /v1/service/logs instead of
/v1/extension/logs, enabling log viewing for core services. Add
container_name to SERVICES dict for accurate name resolution. Replace
bare except with specific urllib error types for proper error
propagation. Add Docker Compose label spoofing prevention.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@Lightheartdevs Lightheartdevs left a comment

Choose a reason for hiding this comment

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

Audit: APPROVE — good security hardening

The error handling rewrite is a significant improvement — forwarding actual HTTP status codes instead of always returning 503. The Docker Compose label spoofing prevention is a valuable addition, correctly handling both dict and list label formats in YAML.

Adding container_name to SERVICES dict enables core service log viewing through the same endpoint as extensions.

Minor notes (non-blocking):

  • The f-string in URLError fallback uses dream-{service_id} which may not match the actual container name (e.g., dream-webui vs the real name). Minor UX issue in error messages.

Depends on #806. Should merge after it.

Copy link
Copy Markdown
Collaborator

@Lightheartdevs Lightheartdevs left a comment

Choose a reason for hiding this comment

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

Revised Audit: REQUEST CHANGES — status code leakage + XSS risk

Withdrawing my earlier approval after deeper review.

Bug 1 (MEDIUM): Raw host-agent status codes forwarded to dashboard client
The HTTPError handler forwards exc.code verbatim. A 500 from the host agent (containing internal paths, Docker errors, stderr snippets) is sent directly to the client. A 401/403 (agent-to-agent auth failure) confuses the dashboard client into thinking its own auth is bad.

Fix: Clamp forwarded status codes — map 4xx from host agent to 502 (bad gateway), map 5xx to 502. Only forward sanitized error strings:

except HTTPError as exc:
    status = 502 if exc.code >= 400 else exc.code
    raise HTTPException(status_code=status, detail=f"Host agent error: {exc.code}")

Security (LOW-MEDIUM): XSS via Docker log output
Docker log content is proxied from host agent to dashboard client. If the frontend renders logs via innerHTML or dangerouslySetInnerHTML, a malicious container could inject <script> tags in its own logs. Verify the frontend uses text rendering (not HTML) for log display.

Info: container_name in SERVICES dict is dead code for this PR
The field is added to the dict but never referenced by the log fetching logic — the host agent resolves container names independently via Docker label lookup. Not a bug, but unused.

Info: Strict merge ordering required
PR #806 must merge first or /v1/service/logs won't exist and ALL log requests return 404.

What's good: Label spoofing prevention is solid (handles both dict and list formats, covers all com.docker.compose.* labels). SSRF risk is zero (URL from server config, not user input). HTML error page fallback works correctly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@yasinBursali
Copy link
Copy Markdown
Contributor Author

Addressing review feedback

Bug 1 (Raw status codes forwarded) — Fixed:
Changed status_code=exc.code to status_code=502 in the HTTPError handler. All host-agent errors now return 502 (Bad Gateway) to the dashboard client, preventing:

  • 401/403 from host agent confusing the dashboard into thinking its own auth failed
  • 500 from host agent leaking internal paths or Docker error details

Error detail string is still forwarded (sanitized via err_body.get("error", ...)) for debugging, but the status code is always 502.

Re: XSS via Docker log output — The dashboard frontend renders logs using text content (React's default JSX escaping), not innerHTML or dangerouslySetInnerHTML. No XSS vector.

Re: container_name in SERVICES dict — This field is consumed by PR #810 (resources.py) for the container-to-service reverse mapping. Required merge order: #806#808#810.

Copy link
Copy Markdown
Collaborator

@Lightheartdevs Lightheartdevs left a comment

Choose a reason for hiding this comment

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

Re-audit: APPROVE — fix verified

HTTPError handler now returns 502 (Bad Gateway) instead of forwarding raw host-agent status codes. Prevents internal error leakage and auth confusion. Label spoofing prevention is solid.

CI all green. Depends on #806.

@Lightheartdevs
Copy link
Copy Markdown
Collaborator

Note: The Rust dashboard-api rewrite (#821) merged and deleted the Python files this PR modifies.

This PR's changes were entirely to Python files (config.py, routers/extensions.py) that no longer exist. The status code clamping and label spoofing prevention need to be reimplemented in the Rust codebase at crates/dashboard-api/src/routes/extensions.rs. Note: the Rust rewrite is also missing the compose security scanning that the Python version had — this is a security regression that should be addressed.

Please rebase or rewrite against the current main branch.

@Lightheartdevs Lightheartdevs merged commit 43bb3c3 into Light-Heart-Labs:main Apr 6, 2026
28 checks passed
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