{"schema":"threatline.record/2","record":{"id":"CVE-2026-112951","aliases":["GHSA-vmgj-9cwm-cf3h"],"summary":"ApostropheCMS: Color field validation bypass allows stored XSS via HTML appended after rgb()/hsl() prefix","details":"### Summary\nThe color field accepts any string that *begins* with a valid `rgb()` / `hsl()` color, because `TinyColor.isValid` does not anchor its match at the end of the string. An authenticated user with edit rights can store arbitrary HTML after a valid color prefix. When the stored value is rendered , the payload executes, resulting in stored XSS.\n\n### Details\nServer-side validation of the color field relies on TinyColor:\n\n```js\nconst { TinyColor } = require('@ctrl/tinycolor');\nif (!new TinyColor(value).isValid) throw self.apos.error('invalid');\n```\n\nTinyColor's `rgb`/`hsl` matchers are not anchored at the end of the string, while hex matchers are. Tested with @ctrl/tinycolor\n\n| Input | `isValid` |\n|---|---|\n| `rgb(1,2,3)<img src=x onerror=alert(1)>` | `true` |\n| `rgb(1,2,3)</textarea><script>alert(1)</script>` | `true` |\n| `hsl(0,100%,50%);--x:1}</style><script>alert(1)</script>` | `true` |\n| `#ff0000<img src=x onerror=alert(1)>` | `false` |\n\nThe value is saved to MongoDB unchanged (not normalized to hex8).\n\n### Relation to GHSA-97v6-998m-fp4g\nGHSA-97v6 addressed CSS custom property injection escaping a `<style>` context. The root cause (validation that accepts trailing content after a valid color) still exists in <= 4.32.2, so [FILL IN: either \"the style-tag sink remains exploitable via `</style>`\" OR \"a separate HTML sink at X is exploitable\"]. Fixing the validation (requiring the full string to be a color, or storing only TinyColor's normalized output) would close both.\n\n### Impact\nAny user with edit permission can plant persistent JavaScript that runs in the browser of visitors and administrators viewing the affected page, enabling session hijacking and actions as an admin.\n\n### Suggested fix\nReject values unless the entire string is a valid color, e.g. normalize on save:\n```js\nconst c = new TinyColor(value);\nif (!c.isValid) throw self.apos.error('invalid');\nvalue = c.toHex8String(); // store only normalized output\n```","categories":["Libraries & packages"],"product":"apostrophe","vendor":"apostrophecms","ecosystem":"npm","affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-vmgj-9cwm-cf3h","source_modified_at":"2026-10-21T17:05:44Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"apostrophe"},"range":"< 4.34.0"},{"source":"CVE List","source_url":"https://www.cve.org/CVERecord?id=CVE-2026-112951","source_modified_at":"2026-10-20T16:43:32.126Z","fetched_at":"2026-10-23T18:45:31.882637Z","product":"apostrophe","vendor":"apostrophecms","versions":[{"version":"< 4.34.0","status":"affected"}],"defaultStatus":null},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-vmgj-9cwm-cf3h","source_modified_at":"2026-10-21T17:15:08.647725268Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"apostrophe","purl":"pkg:npm/apostrophe"},"ranges":[{"events":[{"introduced":"0"},{"fixed":"4.34.0"}],"type":"SEMVER"}],"versions":[]}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-vmgj-9cwm-cf3h","source_modified_at":"2026-10-21T17:05:44Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"apostrophe"},"version":"4.34.0"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-vmgj-9cwm-cf3h","source_modified_at":"2026-10-21T17:15:08.647725268Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"apostrophe","purl":"pkg:npm/apostrophe"},"version":"4.34.0"}],"cvss":5.4,"severity":"Medium","kev":{"listed":false},"epss":{"probability":0.00303,"percentile":0.21926,"score_date":"2026-10-23"},"published_at":"2026-10-20T15:33:09.945Z","modified_at":"2026-10-21T17:15:08.647725268Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/advisories/GHSA-vmgj-9cwm-cf3h","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-21T17:05:44Z"},{"source":"CVE List","url":"https://www.cve.org/CVERecord?id=CVE-2026-112951","fetched_at":"2026-10-23T18:45:31.882637Z","source_modified_at":"2026-10-20T16:43:32.126Z"},{"source":"OSV","url":"https://osv.dev/vulnerability/GHSA-vmgj-9cwm-cf3h","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-21T17:15:08.647725268Z"},{"source":"EPSS","url":"https://api.first.org/data/v1/epss?cve=CVE-2026-112951","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-23"}],"sources":["GitHub Advisory Database","CVE List","OSV","EPSS"],"references":[{"source":"CVE List","url":"https://github.com/apostrophecms/apostrophe/security/advisories/GHSA-vmgj-9cwm-cf3h","tags":["exploit"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-112951","source":"GitHub Advisory Database"},{"source":"CVE List","url":"https://github.com/apostrophecms/apostrophe/commit/06e4b377f3c360f9e89c32dccd7e505ad5ddf498","tags":["x_refsource_MISC"]},{"source":"CVE List","url":"https://github.com/apostrophecms/apostrophe/commit/40b989b1a9dd23d30029b8ce7668634265cfe5ea","tags":["x_refsource_MISC"]},{"source":"CVE List","url":"https://github.com/apostrophecms/apostrophe/releases/tag/apostrophe@4.34.0","tags":["x_refsource_MISC"]},{"url":"https://github.com/advisories/GHSA-vmgj-9cwm-cf3h","source":"GitHub Advisory Database"}],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"cve_state":"PUBLISHED","ssvc":{"exploitation":"poc","automatable":"no","technical_impact":"partial","assessed_at":"2026-10-20T16:42:47.280895Z"},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":"ecosystem","confidence":"heuristic","reviewed":false},"change_timestamp":"2026-10-21T17:15:08.647725268Z","content_hash":"d3914c2dd47852c02678a5ebc3c3bc2cef84c96f497e86ad228602b352481d9b"}}
