GHSA-Q2PJ-8V84-9MH5

Vulnerability from github – Published: 2026-05-18 14:19 – Updated: 2026-06-09 10:30
VLAI
Summary
Arcane Backend: Unauthenticated reflected XSS via SVG color parameter enables admin account takeover
Details

Summary

The unauthenticated GET /api/app-images/logo endpoint reflects a user-supplied color query parameter into the body of an SVG document via strings.ReplaceAll with no escaping. The substitution lands inside a <style> element of the embedded logo.svg, allowing an attacker to close the style block and inject executable <script> content. Because the response is served as image/svg+xml and Arcane sets no Content-Security-Policy or X-Content-Type-Options headers, navigating a logged-in admin victim to a crafted URL executes attacker-controlled JavaScript in Arcane's origin and rides the victim's HttpOnly JWT cookie to fully compromise the admin account.

Details

The route is registered in backend/internal/huma/handlers/appimages.go:53-61 with an explicitly empty security requirement, marking it as public:

huma.Register(api, huma.Operation{
    OperationID: "get-logo",
    Method:      http.MethodGet,
    Path:        "/app-images/logo",
    ...
    Security:    []map[string][]string{}, // explicit: no auth
}, h.GetLogo)

backend/internal/huma/middleware/auth.go:209-213 honors the empty Security value by returning reqs.isRequired == false and short-circuiting with next(ctx), so no JWT/API-key check runs.

GetLogoInput.Color (appimages.go:23) is declared with no validation tags:

type GetLogoInput struct {
    Full  bool   `query:"full" default:"false" ...`
    Color string `query:"color" doc:"Optional accent color override ..."`
}

The handler passes the value straight through getImageWithColor → ApplicationImagesService.GetImageWithColor → applyAccentColorToSVG (backend/internal/services/app_images_service.go:79-105):

svgStr = strings.ReplaceAll(svgStr, "fill:#6D28D9", fmt.Sprintf("fill:%s", accentColor))
svgStr = strings.ReplaceAll(svgStr, "fill:#6d28d9", fmt.Sprintf("fill:%s", accentColor))

The bundled backend/resources/images/logo.svg contains:

<style id="style1" type="text/css">.st0{fill:#6d28d9}</style>

so a color value like red}</style><script>fetch('/api/users',...)</script><style>x{ produces a valid SVG that closes the <style> element and embeds a <script> element. The response Content-Type is image/svg+xml (from pkg/utils/image/image_util.go), and a grep of the backend confirms no Content-Security-Policy, X-Content-Type-Options, or framing headers are emitted on any route.

Browsers execute scripts in SVG documents loaded as top-level navigations or via <iframe src=…> / window.open(…). The execution context is origin(arcane-host), so the victim's __Host-token / token HttpOnly JWT cookie (recognized by extractTokenFromCookieHeaderInternal at auth.go:274-286) is automatically attached to subsequent same-origin fetch() calls. From there the attacker can invoke any privileged API the victim possesses — most damagingly POST /api/users to create a new admin account, after which the attacker has standalone admin access to manage Docker containers, registries, GitOps secrets, and SSH/registry credentials stored by Arcane.

Impact

  • Same-origin script execution from an unauthenticated, reachable URL — only user interaction (clicking/visiting the crafted link) is required.
  • Full session-riding against any authenticated user, including admins. Because Arcane manages Docker daemons, container exec, image registries, and GitOps repositories, an attacker who lands script execution as an admin victim can:
  • Create persistent attacker-controlled admin accounts via POST /api/users.
  • Read/modify secrets stored in environments, registries, and Git repositories the admin can access.
  • Start or exec into containers on connected Docker hosts.
  • HttpOnly cookies do not mitigate the issue — cookies are auto-attached to same-origin fetch(). Absence of CSP and X-Content-Type-Options: nosniff removes available defenses-in-depth.

Defense-in-depth — add to all responses (and especially to /api/app-images/*):

  • X-Content-Type-Options: nosniff
  • Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; img-src 'self' data: on the SVG image responses (or the most permissive policy compatible with the frontend on app routes).
  • Consider serving these images with Content-Disposition: inline and from a separate cookie-less origin to remove the same-origin session-riding primitive entirely.

Also enforce the same allowlist on the settings write path (SettingsService → AccentColor) so a stored XSS variant cannot be introduced via the settings API.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 1.18.1"
      },
      "package": {
        "ecosystem": "Go",
        "name": "github.com/getarcaneapp/arcane/backend"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "1.19.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-45627"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-79"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-05-18T14:19:26Z",
    "nvd_published_at": "2026-05-29T18:17:10Z",
    "severity": "HIGH"
  },
  "details": "## Summary\n\nThe unauthenticated `GET /api/app-images/logo` endpoint reflects a user-supplied `color` query parameter into the body of an SVG document via `strings.ReplaceAll` with no escaping. The substitution lands inside a `\u003cstyle\u003e` element of the embedded `logo.svg`, allowing an attacker to close the style block and inject executable `\u003cscript\u003e` content. Because the response is served as `image/svg+xml` and Arcane sets no Content-Security-Policy or `X-Content-Type-Options` headers, navigating a logged-in admin victim to a crafted URL executes attacker-controlled JavaScript in Arcane\u0027s origin and rides the victim\u0027s HttpOnly JWT cookie to fully compromise the admin account.\n\n## Details\n\nThe route is registered in `backend/internal/huma/handlers/appimages.go:53-61` with an explicitly empty security requirement, marking it as public:\n\n```go\nhuma.Register(api, huma.Operation{\n    OperationID: \"get-logo\",\n    Method:      http.MethodGet,\n    Path:        \"/app-images/logo\",\n    ...\n    Security:    []map[string][]string{}, // explicit: no auth\n}, h.GetLogo)\n```\n\n`backend/internal/huma/middleware/auth.go:209-213` honors the empty `Security` value by returning `reqs.isRequired == false` and short-circuiting with `next(ctx)`, so no JWT/API-key check runs.\n\n`GetLogoInput.Color` (`appimages.go:23`) is declared with no validation tags:\n\n```go\ntype GetLogoInput struct {\n    Full  bool   `query:\"full\" default:\"false\" ...`\n    Color string `query:\"color\" doc:\"Optional accent color override ...\"`\n}\n```\n\nThe handler passes the value straight through `getImageWithColor` \u2192 `ApplicationImagesService.GetImageWithColor` \u2192 `applyAccentColorToSVG` (`backend/internal/services/app_images_service.go:79-105`):\n\n```go\nsvgStr = strings.ReplaceAll(svgStr, \"fill:#6D28D9\", fmt.Sprintf(\"fill:%s\", accentColor))\nsvgStr = strings.ReplaceAll(svgStr, \"fill:#6d28d9\", fmt.Sprintf(\"fill:%s\", accentColor))\n```\n\nThe bundled `backend/resources/images/logo.svg` contains:\n\n```xml\n\u003cstyle id=\"style1\" type=\"text/css\"\u003e.st0{fill:#6d28d9}\u003c/style\u003e\n```\n\nso a `color` value like `red}\u003c/style\u003e\u003cscript\u003efetch(\u0027/api/users\u0027,...)\u003c/script\u003e\u003cstyle\u003ex{` produces a valid SVG that closes the `\u003cstyle\u003e` element and embeds a `\u003cscript\u003e` element. The response Content-Type is `image/svg+xml` (from `pkg/utils/image/image_util.go`), and a grep of the backend confirms no `Content-Security-Policy`, `X-Content-Type-Options`, or framing headers are emitted on any route.\n\nBrowsers execute scripts in SVG documents loaded as top-level navigations or via `\u003ciframe src=\u2026\u003e` / `window.open(\u2026)`. The execution context is `origin(arcane-host)`, so the victim\u0027s `__Host-token` / `token` HttpOnly JWT cookie (recognized by `extractTokenFromCookieHeaderInternal` at `auth.go:274-286`) is automatically attached to subsequent same-origin `fetch()` calls. From there the attacker can invoke any privileged API the victim possesses \u2014 most damagingly `POST /api/users` to create a new admin account, after which the attacker has standalone admin access to manage Docker containers, registries, GitOps secrets, and SSH/registry credentials stored by Arcane.\n\n## Impact\n\n- Same-origin script execution from an unauthenticated, reachable URL \u2014 only user interaction (clicking/visiting the crafted link) is required.\n- Full session-riding against any authenticated user, including admins. Because Arcane manages Docker daemons, container exec, image registries, and GitOps repositories, an attacker who lands script execution as an admin victim can:\n  - Create persistent attacker-controlled admin accounts via `POST /api/users`.\n  - Read/modify secrets stored in environments, registries, and Git repositories the admin can access.\n  - Start or exec into containers on connected Docker hosts.\n- HttpOnly cookies do not mitigate the issue \u2014 cookies are auto-attached to same-origin `fetch()`. Absence of CSP and `X-Content-Type-Options: nosniff` removes available defenses-in-depth.\n\nDefense-in-depth \u2014 add to all responses (and especially to `/api/app-images/*`):\n\n- `X-Content-Type-Options: nosniff`\n- `Content-Security-Policy: default-src \u0027none\u0027; style-src \u0027unsafe-inline\u0027; img-src \u0027self\u0027 data:` on the SVG image responses (or the most permissive policy compatible with the frontend on app routes).\n- Consider serving these images with `Content-Disposition: inline` and from a separate cookie-less origin to remove the same-origin session-riding primitive entirely.\n\nAlso enforce the same allowlist on the settings write path (`SettingsService` \u2192 `AccentColor`) so a stored XSS variant cannot be introduced via the settings API.",
  "id": "GHSA-q2pj-8v84-9mh5",
  "modified": "2026-06-09T10:30:32Z",
  "published": "2026-05-18T14:19:26Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/getarcaneapp/arcane/security/advisories/GHSA-q2pj-8v84-9mh5"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-45627"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/getarcaneapp/arcane"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Arcane Backend: Unauthenticated reflected XSS via SVG color parameter enables admin account takeover"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…