{"schema":"threatline.record/2","record":{"id":"CVE-2026-106103","aliases":["GHSA-wmpw-j6qv-mw88"],"summary":"Quasar Framework: Path Traversal / Arbitrary File Write via crafted Icon Genie profile","details":"## Vulnerability Details\n\n**File**: `icongenie/lib/utils/get-assets-files.js` (line 35, `absoluteName: join(appDir, asset.folder, asset.name)`)\n**Validation gap**: `icongenie/lib/utils/validate-profile-object.js` (`assetsSchema`) — `folder`/`name` only checked with `Joi.string().required().min(1)`, no restriction on `..` sequences or absolute paths\n**Entry point**: `icongenie/lib/runner/generate.js` (`generate(argv)`) — `profile.assets = userProfile.assets`, loaded verbatim from a user-supplied JSON file via `--profile <file>`\n\n### Root Cause\n`icongenie generate --profile <file>` loads a JSON \"profile\" describing icon/splashscreen assets to generate, where each asset entry has a `folder`/`name` describing where the generated file should be written relative to the Quasar project directory (`appDir`). `getAssetsFiles()` builds the write target with `join(appDir, asset.folder, asset.name)`. Node's `path.join` normalizes `..` segments arithmetically and does not clamp the result to stay inside `appDir`. The only validation before this (`validateProfileObject` → Joi `assetsSchema`) checks that `folder`/`name` are non-empty strings, with no `..` rejection and no containment check against `appDir`.\n\nA profile setting `folder: \"../../../../../../tmp/pwned-by-icongenie\"` sails through validation unmodified, and the generator writes attacker-influenced icon/splashscreen content to that path via a direct `writeFile`/`sharp().toFile()` call.\n\n### Attack Scenario\n1. Attacker publishes a \"ready-made Icon Genie profile\" (gist, starter-kit repo, support forum attachment) that looks like a normal icon-generation config but includes an asset entry with a traversal `folder`.\n2. A developer working on a Quasar project runs `icongenie generate --profile malicious-profile.json` (a normal, documented workflow) inside their project.\n3. `getAssetsFiles()` resolves the write target outside the project directory; the generator writes attacker-controlled content to that path — e.g. planting/overwriting shell startup files, cron entries, or CI/build scripts.\n\n### Impact\n- **Type**: CWE-22 Path Traversal / Arbitrary File Write\n- **Auth required**: No network auth — local CLI trust; requires the developer to run icongenie against a profile they didn't fully author/audit themselves\n- **Consequence**: Arbitrary file write/overwrite at any path the running user can write to, scoped by the number of `..` segments — can lead to persistence (cron/shell rc file) or supply-chain-style code execution if the written file is later executed/sourced.\n\n### Vulnerable Code (`icongenie/lib/utils/get-assets-files.js`)\n```js\nexport function getAssetsFiles(assets) {\n  ...\n  return list.map(({ tag, ...asset }) => {\n    const file = {\n      ...asset,\n      relativeName: join(asset.folder, asset.name),\n      absoluteName: join(appDir, asset.folder, asset.name)   // no containment check\n    }\n    ...\n  })\n}\n```\n\n### Recommended Fix\n```js\nimport { resolve, sep } from 'node:path'\n\nconst absoluteName = resolve(appDir, asset.folder, asset.name)\n\nif (absoluteName !== appDir && !absoluteName.startsWith(appDir + sep)) {\n  fatal(`Profile asset escapes the project folder: \"${asset.folder}/${asset.name}\"`)\n}\n```\n\n### Verification\nConfirmed end-to-end on v2.21.1 by running the real, unmodified `icongenie generate()` function (from `icongenie/lib/runner/generate.js`) against a scratch Quasar project containing a crafted `malicious-profile.json` with `\"folder\": \"../../outside-target-marker\"`. icongenie's own console output self-reported the traversal (`Generated svg: ../../outside-target-marker/pwned-outside-project.svg`), and the generated SVG file was independently verified on disk two directory levels outside the project folder.\n\nA fix branch (`fix/icongenie-path-traversal-asset-folder`) is ready with the minimal patch above. Re-running the same malicious profile against the patched code now aborts immediately with `Profile asset escapes the project folder: \"../../outside-target-marker/pwned-outside-project.svg\"` and writes nothing outside the project, while a legitimate profile (`folder: \"public/icons\"`) continues to work exactly as before.","categories":["Libraries & packages"],"product":"@quasar/icongenie","vendor":"quasarframework","ecosystem":"npm","affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-wmpw-j6qv-mw88","source_modified_at":"2026-10-07T16:14:15Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"@quasar/icongenie"},"range":"< 6.1.1"},{"source":"CVE List","source_url":"https://www.cve.org/CVERecord?id=CVE-2026-106103","source_modified_at":"2026-10-06T19:42:52.902Z","fetched_at":"2026-10-23T18:45:31.882637Z","product":"quasar","vendor":"quasarframework","versions":[{"version":"< 2.22.0","status":"affected"}],"defaultStatus":null},{"source":"CVE List","source_url":"https://www.cve.org/CVERecord?id=CVE-2026-106103","source_modified_at":"2026-10-06T19:42:52.902Z","fetched_at":"2026-10-23T18:45:31.882637Z","product":"icongenie","vendor":"@quasar","versions":[{"version":"< 6.1.1","status":"affected"}],"defaultStatus":null},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-wmpw-j6qv-mw88","source_modified_at":"2026-10-07T16:30:04.720529512Z","fetched_at":"2026-10-23T18:45:31.882637Z","package":{"name":"@quasar/icongenie","ecosystem":"npm","purl":"pkg:npm/%40quasar/icongenie"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"6.1.1"}]}],"versions":[]}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-wmpw-j6qv-mw88","source_modified_at":"2026-10-07T16:14:15Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"@quasar/icongenie"},"version":"6.1.1"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-wmpw-j6qv-mw88","source_modified_at":"2026-10-07T16:30:04.720529512Z","fetched_at":"2026-10-23T18:45:31.882637Z","package":{"name":"@quasar/icongenie","ecosystem":"npm","purl":"pkg:npm/%40quasar/icongenie"},"version":"6.1.1"}],"cvss":7.1,"severity":"High","kev":{"listed":false},"epss":{"probability":0.00288,"percentile":0.20243,"score_date":"2026-10-23"},"published_at":"2026-10-06T17:11:03.821Z","modified_at":"2026-10-07T16:30:04.720529512Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/advisories/GHSA-wmpw-j6qv-mw88","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-07T16:14:15Z"},{"source":"CVE List","url":"https://www.cve.org/CVERecord?id=CVE-2026-106103","fetched_at":"2026-10-23T18:45:31.882637Z","source_modified_at":"2026-10-06T19:42:52.902Z"},{"source":"OSV","url":"https://osv.dev/vulnerability/GHSA-wmpw-j6qv-mw88","fetched_at":"2026-10-23T18:45:31.882637Z","source_modified_at":"2026-10-07T16:30:04.720529512Z"},{"source":"EPSS","url":"https://api.first.org/data/v1/epss?cve=CVE-2026-106103","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/quasarframework/quasar/security/advisories/GHSA-wmpw-j6qv-mw88","tags":["x_refsource_CONFIRM"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-106103","source":"GitHub Advisory Database"},{"source":"CVE List","url":"https://github.com/quasarframework/quasar/commit/87c89a80ec84f1eb7dbe258e5367161b0598ceb3","tags":["x_refsource_MISC"]},{"source":"CVE List","url":"https://github.com/quasarframework/quasar/releases/tag/@quasar/icongenie-v6.1.1","tags":["x_refsource_MISC"]},{"url":"https://github.com/advisories/GHSA-wmpw-j6qv-mw88","source":"GitHub Advisory Database"}],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"cve_state":"PUBLISHED","ssvc":{"exploitation":"none","automatable":"no","technical_impact":"partial","assessed_at":"2026-10-06T19:42:43.611562Z"},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":"ecosystem","confidence":"heuristic","reviewed":false},"change_timestamp":"2026-10-07T16:30:04.720529512Z","content_hash":"d630e249c2f7c71e5691560e49fb3dc7dc76de3d06fa82539e245d20dd63fe5f"}}
