Skip to content

Security audit: expire dashboard sessions, protect mutations, scope MCP, cap webhook bodies #339

Closed

Description

@pilipilisbot

Security audit · 2026-11-07

Scope: static review of main at 2f358bc (v0.88.1), plus read-only checks of the installed dashboard service and non-secret deployment settings. No live attack, credential rotation, or configuration change was performed. Findings below are code-level risks, not claims of observed exploitation.

P1 — Session authorization is indefinitely reusable and can outlive policy changes

The signed dashboard session stores is_admin and the login but no issue/expiry time (encoding). Every request trusts the cookie's embedded profile (current_profile); allowlists and admin membership are evaluated only during OAuth callback (callback). The session cookie has no max_age/expires (cookie). A previously issued/stolen cookie remains valid server-side after a user's access is revoked, until the signing key is rotated. Browser session-cookie lifetime is not a server-side expiry.

  • Add signed iat/exp, enforce a short server-side lifetime, and provide logout/revocation (or a session version).
  • Re-evaluate dashboard allowlist and admin status on requests or at a bounded interval; document how org/team removal takes effect.
  • Add tests for expiry, allowlist removal, admin demotion, and key rotation.

P2 — State-changing cookie-authenticated routes have no explicit CSRF/origin check

Admin and user actions, including executor pause/resume/resize, job retry/dismiss/cancel, and MCP token creation, rely on a signed session cookie but do not validate a CSRF token or Origin/Referer. SameSite=Lax is present, but does not prevent same-site, cross-origin requests (e.g. a less-trusted sibling subdomain). This is conditional on a victim visiting such an origin; no exploit was attempted.

  • Require same-origin Origin validation (with carefully handled absent headers) and/or a per-session CSRF token for all cookie-authenticated mutation methods.
  • Keep bearer-token MCP and signed webhook endpoints outside that cookie-CSRF mechanism; add cross-origin regression tests for each mutation class.

P2 — MCP token ownership is not used to scope repository knowledge

/api/mcp authenticates a bearer token but discards the returned owner record (handler). MCPServer then lists all repositories and accepts any repository in resources/read and list_knowledge (methods). Thus every valid MCP token can read all stored bridge knowledge, including other repositories. If global sharing is intentional, the current per-user token ownership is misleading and needs an explicit trust decision.

  • Decide/document whether knowledge is global or repository-scoped; if scoped, bind token owner to allowed repos and enforce the check in both tool and resource paths.
  • Add tests with two token owners and two repositories proving no cross-scope reads.

P3 — Webhook body limit is enforced after full body buffering

Without a trustworthy Content-Length, the ingress calls await request.body() before comparing its size to webhook_max_bytes (handler). This can allocate much more than the configured limit before rejecting an unauthenticated request. The repository's example nginx route does not set an explicit client_max_body_size for the endpoint. Actual exposure depends on the deployed proxy limits; this was not load-tested.

  • Stream request chunks with an incremental hard cap, return 413 before full buffering, and set an explicit matching proxy body limit.
  • Test oversized chunked/no-Content-Length requests without reading the whole stream.

Controls observed / limits

  • Webhook HMAC-SHA256 uses constant-time comparison; OAuth callback checks a signed state cookie. The installed deployment has dashboard user/admin allowlists configured, a restricted (0600) env file, and a loopback dashboard listener. These mitigate some exposure but do not remove the findings above.
  • npm audit --omit=dev reported 0 production frontend dependency advisories for the lockfile on this date. This is not a Python dependency audit or penetration test.
  • Follow-up automation/linting overlaps with Add linting, formatting checks, and lightweight security checks to CI #195; this issue is for the specific security fixes above.

Activity

  1. giscebot commented on Nov 7, 2026

    @giscebot
    Collaborator

    Implemented in #340 (f02e243).

    The PR adds bounded/versioned dashboard sessions, request-time allowlist and admin-demotion checks, logout, fail-closed same-origin validation for cookie mutations, incremental webhook body limiting, and the matching nginx limit. Local validation is green: 713 Python tests, 79 dashboard tests, and a production dashboard build.

    GitHub CI is also green on the dashboard job and Python 3.11/3.12.

    For MCP I made the trust decision explicit instead of inventing an ACL from job authorship: knowledge remains deployment-global, consistent with the dashboard read model, while the linked user is now documented and labelled as a lifecycle owner only. Real repository isolation would need one ACL enforced by both dashboard and MCP.

    Operational caveats are recorded in the PR: existing legacy cookies will require re-login, and the nginx cap is not active until the proxy configuration is deployed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions