{"id":"GHSA-g485-8j3v-p6x8","aliases":[],"status":"published","title":"@tdurieux/anonymous_github Vulnerable to XSS via Unsanitized GitHub Repository Content Rendering in Anonymous GitHub Origin","description":"### Summary\n\nAnonymous GitHub fetches repository content (e.g., markdown files) from GitHub's API and renders it without sanitization. On the client side, markdown is parsed with `marked` (with `sanitize: false`) and injected into the DOM via `$sce.trustAsHtml()` + `ng-bind-html`, bypassing AngularJS's built-in XSS protection. An attacker can craft a malicious GitHub repository whose README executes arbitrary JavaScript in the Anonymous GitHub origin.\n\n### Details\n\n#### README fetched from GitHub API\n\nThe server fetches the README via GitHub's REST API and stores the raw markdown in MongoDB:\n\n```typescript\n// https://github.com/tdurieux/anonymous_github/blob/b2d77faa6c6f35ad9ae6ed46e3a3fa4681ac84c2/src/core/source/GitHubRepository.ts#L162-L174\nconst ghRes = await oct.repos.getReadme({\n  owner: this.owner,\n  repo: this.repo,\n  ref: selected?.commit,\n});\nconst readme = Buffer.from(\n  ghRes.data.content,\n  ghRes.data.encoding as BufferEncoding\n).toString(\"utf-8\");\nselected.readme = readme;\nawait model.save();\n```\n\nIt is then served to the client with no sanitization:\n\n```typescript\n// https://github.com/tdurieux/anonymous_github/blob/b2d77faa6c6f35ad9ae6ed46e3a3fa4681ac84c2/src/server/routes/repository-private.ts#L254-L260\nreturn res.send(\n  await repo.readme({\n    accessToken: token,\n    force: req.query.force == \"1\",\n    branch: req.query.branch as string,\n  })\n);\n```\n\n#### Client-side rendering via `$sce.trustAsHtml()` + `ng-bind-html`\n\nThe client fetches the raw README, parses it with `renderMD()` (which uses `marked` with `sanitize: false`), then bypasses AngularJS sanitization:\n\n```javascript\n// https://github.com/tdurieux/anonymous_github/blob/b2d77faa6c6f35ad9ae6ed46e3a3fa4681ac84c2/public/script/app.js#L1219-L1226\nconst res = await $http.get(`/api/repo/${o.owner}/${o.repo}/readme`, {\n  params: { force: force === true ? \"1\" : \"0\", branch: $scope.source.branch },\n});\n$scope.readme = res.data;\n```\n\n```javascript\n// https://github.com/tdurieux/anonymous_github/blob/b2d77faa6c6f35ad9ae6ed46e3a3fa4681ac84c2/public/script/app.js#L1339-L1343\nconst html = renderMD(\n  $scope.anonymize_readme,\n  `https://github.com/${o.owner}/${o.repo}/raw/${$scope.source.branch}/`\n);\n$scope.html_readme = $sce.trustAsHtml(html);  // sink: bypasses Angular XSS protection\n```\n\nThe `renderMD()` function explicitly disables sanitization:\n\n```javascript\n// https://github.com/tdurieux/anonymous_github/blob/b2d77faa6c6f35ad9ae6ed46e3a3fa4681ac84c2/public/script/utils.js#L165-L176\nmarked.setOptions({\n  sanitize: false,  // HTML in markdown is preserved as-is\n  // ...\n});\nreturn marked.parse(md, { renderer });\n```\n\nThe resulting HTML is bound to the DOM via `ng-bind-html`, which trusts the string marked by `$sce.trustAsHtml()` and inserts it as innerHTML.\n\n### Impact\n\n1. **Stored XSS**: Any malicious GitHub repository can execute JavaScript in the Anonymous GitHub origin when a user anonymizes it or views its content\n2. **Account Takeover**: Steal authentication tokens and session cookies\n3. **Data Exfiltration**: Access other users' anonymization configurations and private repository data via `/api/user` and `/api/repo/list`\n\n### Proof of Concept\n\n![poc-xss-anonymous-github](https://github.com/user-attachments/assets/c1bf3ed9-4e1e-4c8e-87f0-782a8d5f6ead)\n\n1. Create a GitHub repository with a malicious `README.md`:\n\n```markdown\n# Innocent README\n\u003cimg src=x onerror=\"alert(document.domain)\"\u003e\n```\n\n2. On Anonymous GitHub, enter the malicious repository URL to anonymize it\n3. The XSS executes immediately when the README preview is rendered on the anonymize page\n\n### Remediation\n\n1. Sanitize markdown output with DOMPurify before rendering (the dependency already exists but is unused)\n2. Serve HTML files with `Content-Disposition: attachment` or in a sandboxed iframe on a separate origin\n3. Replace `$sce.trustAsHtml()` with proper `ngSanitize` usage\n4. HTML-escape filenames and paths in directory listing templates\n5. Add Content Security Policy headers\n\n### Credits\n\nZhengyu Liu, Jingcheng Yang","severity":{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N","level":"high","from":"ghsa"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-79","CWE-80"],"published":"2026-05-05T18:28:32Z","modified":"2026-05-05T18:47:42Z","fixes":[{"ecosystem":"npm","package":"@tdurieux/anonymous_github","fixed_in":["2.3.0"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"@tdurieux/anonymous_github","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"2.2.0"},{"last_affected":"2.2.0"},{"fixed":"2.3.0"}]}],"versions":["2.2.0"]},{"source":"osv","ecosystem":"npm","package":"@tdurieux/anonymous_github","ranges":[{"type":"SEMVER","events":[{"introduced":"2.2.0"},{"fixed":"2.3.0"}]}],"versions":["2.2.0"]}],"references":[{"url":"https://github.com/tdurieux/anonymous_github/security/advisories/GHSA-g485-8j3v-p6x8","tags":["web"]},{"url":"https://github.com/advisories/GHSA-g485-8j3v-p6x8"},{"url":"https://github.com/tdurieux/anonymous_github","tags":["package"]}],"sources":{"ghsa":[{"id":"GHSA-g485-8j3v-p6x8","title":"@tdurieux/anonymous_github Vulnerable to XSS via Unsanitized GitHub Repository Content Rendering in Anonymous GitHub Origin","description":"### Summary\n\nAnonymous GitHub fetches repository content (e.g., markdown files) from GitHub's API and renders it without sanitization. On the client side, markdown is parsed with `marked` (with `sanitize: false`) and injected into the DOM via `$sce.trustAsHtml()` + `ng-bind-html`, bypassing AngularJS's built-in XSS protection. An attacker can craft a malicious GitHub repository whose README executes arbitrary JavaScript in the Anonymous GitHub origin.\n\n### Details\n\n#### README fetched from GitHub API\n\nThe server fetches the README via GitHub's REST API and stores the raw markdown in MongoDB:\n\n```typescript\n// https://github.com/tdurieux/anonymous_github/blob/b2d77faa6c6f35ad9ae6ed46e3a3fa4681ac84c2/src/core/source/GitHubRepository.ts#L162-L174\nconst ghRes = await oct.repos.getReadme({\n  owner: this.owner,\n  repo: this.repo,\n  ref: selected?.commit,\n});\nconst readme = Buffer.from(\n  ghRes.data.content,\n  ghRes.data.encoding as BufferEncoding\n).toString(\"utf-8\");\nselected.readme = readme;\nawait model.save();\n```\n\nIt is then served to the client with no sanitization:\n\n```typescript\n// https://github.com/tdurieux/anonymous_github/blob/b2d77faa6c6f35ad9ae6ed46e3a3fa4681ac84c2/src/server/routes/repository-private.ts#L254-L260\nreturn res.send(\n  await repo.readme({\n    accessToken: token,\n    force: req.query.force == \"1\",\n    branch: req.query.branch as string,\n  })\n);\n```\n\n#### Client-side rendering via `$sce.trustAsHtml()` + `ng-bind-html`\n\nThe client fetches the raw README, parses it with `renderMD()` (which uses `marked` with `sanitize: false`), then bypasses AngularJS sanitization:\n\n```javascript\n// https://github.com/tdurieux/anonymous_github/blob/b2d77faa6c6f35ad9ae6ed46e3a3fa4681ac84c2/public/script/app.js#L1219-L1226\nconst res = await $http.get(`/api/repo/${o.owner}/${o.repo}/readme`, {\n  params: { force: force === true ? \"1\" : \"0\", branch: $scope.source.branch },\n});\n$scope.readme = res.data;\n```\n\n```javascript\n// https://github.com/tdurieux/anonymous_github/blob/b2d77faa6c6f35ad9ae6ed46e3a3fa4681ac84c2/public/script/app.js#L1339-L1343\nconst html = renderMD(\n  $scope.anonymize_readme,\n  `https://github.com/${o.owner}/${o.repo}/raw/${$scope.source.branch}/`\n);\n$scope.html_readme = $sce.trustAsHtml(html);  // sink: bypasses Angular XSS protection\n```\n\nThe `renderMD()` function explicitly disables sanitization:\n\n```javascript\n// https://github.com/tdurieux/anonymous_github/blob/b2d77faa6c6f35ad9ae6ed46e3a3fa4681ac84c2/public/script/utils.js#L165-L176\nmarked.setOptions({\n  sanitize: false,  // HTML in markdown is preserved as-is\n  // ...\n});\nreturn marked.parse(md, { renderer });\n```\n\nThe resulting HTML is bound to the DOM via `ng-bind-html`, which trusts the string marked by `$sce.trustAsHtml()` and inserts it as innerHTML.\n\n### Impact\n\n1. **Stored XSS**: Any malicious GitHub repository can execute JavaScript in the Anonymous GitHub origin when a user anonymizes it or views its content\n2. **Account Takeover**: Steal authentication tokens and session cookies\n3. **Data Exfiltration**: Access other users' anonymization configurations and private repository data via `/api/user` and `/api/repo/list`\n\n### Proof of Concept\n\n![poc-xss-anonymous-github](https://github.com/user-attachments/assets/c1bf3ed9-4e1e-4c8e-87f0-782a8d5f6ead)\n\n1. Create a GitHub repository with a malicious `README.md`:\n\n```markdown\n# Innocent README\n\u003cimg src=x onerror=\"alert(document.domain)\"\u003e\n```\n\n2. On Anonymous GitHub, enter the malicious repository URL to anonymize it\n3. The XSS executes immediately when the README preview is rendered on the anonymize page\n\n### Remediation\n\n1. Sanitize markdown output with DOMPurify before rendering (the dependency already exists but is unused)\n2. Serve HTML files with `Content-Disposition: attachment` or in a sandboxed iframe on a separate origin\n3. Replace `$sce.trustAsHtml()` with proper `ngSanitize` usage\n4. HTML-escape filenames and paths in directory listing templates\n5. Add Content Security Policy headers\n\n### Credits\n\nZhengyu Liu, Jingcheng Yang","status":"published","published":"2026-05-05T18:28:32Z","modified":"2026-05-05T18:28:32Z","cvss":[{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N","level":"high"}],"cwes":["CWE-79","CWE-80"],"updated_at":"2026-11-02T14:40:00.970271Z"}],"osv":[{"id":"GHSA-g485-8j3v-p6x8","title":"@tdurieux/anonymous_github Vulnerable to XSS via Unsanitized GitHub Repository Content Rendering in Anonymous GitHub Origin","description":"### Summary\n\nAnonymous GitHub fetches repository content (e.g., markdown files) from GitHub's API and renders it without sanitization. On the client side, markdown is parsed with `marked` (with `sanitize: false`) and injected into the DOM via `$sce.trustAsHtml()` + `ng-bind-html`, bypassing AngularJS's built-in XSS protection. An attacker can craft a malicious GitHub repository whose README executes arbitrary JavaScript in the Anonymous GitHub origin.\n\n### Details\n\n#### README fetched from GitHub API\n\nThe server fetches the README via GitHub's REST API and stores the raw markdown in MongoDB:\n\n```typescript\n// https://github.com/tdurieux/anonymous_github/blob/b2d77faa6c6f35ad9ae6ed46e3a3fa4681ac84c2/src/core/source/GitHubRepository.ts#L162-L174\nconst ghRes = await oct.repos.getReadme({\n  owner: this.owner,\n  repo: this.repo,\n  ref: selected?.commit,\n});\nconst readme = Buffer.from(\n  ghRes.data.content,\n  ghRes.data.encoding as BufferEncoding\n).toString(\"utf-8\");\nselected.readme = readme;\nawait model.save();\n```\n\nIt is then served to the client with no sanitization:\n\n```typescript\n// https://github.com/tdurieux/anonymous_github/blob/b2d77faa6c6f35ad9ae6ed46e3a3fa4681ac84c2/src/server/routes/repository-private.ts#L254-L260\nreturn res.send(\n  await repo.readme({\n    accessToken: token,\n    force: req.query.force == \"1\",\n    branch: req.query.branch as string,\n  })\n);\n```\n\n#### Client-side rendering via `$sce.trustAsHtml()` + `ng-bind-html`\n\nThe client fetches the raw README, parses it with `renderMD()` (which uses `marked` with `sanitize: false`), then bypasses AngularJS sanitization:\n\n```javascript\n// https://github.com/tdurieux/anonymous_github/blob/b2d77faa6c6f35ad9ae6ed46e3a3fa4681ac84c2/public/script/app.js#L1219-L1226\nconst res = await $http.get(`/api/repo/${o.owner}/${o.repo}/readme`, {\n  params: { force: force === true ? \"1\" : \"0\", branch: $scope.source.branch },\n});\n$scope.readme = res.data;\n```\n\n```javascript\n// https://github.com/tdurieux/anonymous_github/blob/b2d77faa6c6f35ad9ae6ed46e3a3fa4681ac84c2/public/script/app.js#L1339-L1343\nconst html = renderMD(\n  $scope.anonymize_readme,\n  `https://github.com/${o.owner}/${o.repo}/raw/${$scope.source.branch}/`\n);\n$scope.html_readme = $sce.trustAsHtml(html);  // sink: bypasses Angular XSS protection\n```\n\nThe `renderMD()` function explicitly disables sanitization:\n\n```javascript\n// https://github.com/tdurieux/anonymous_github/blob/b2d77faa6c6f35ad9ae6ed46e3a3fa4681ac84c2/public/script/utils.js#L165-L176\nmarked.setOptions({\n  sanitize: false,  // HTML in markdown is preserved as-is\n  // ...\n});\nreturn marked.parse(md, { renderer });\n```\n\nThe resulting HTML is bound to the DOM via `ng-bind-html`, which trusts the string marked by `$sce.trustAsHtml()` and inserts it as innerHTML.\n\n### Impact\n\n1. **Stored XSS**: Any malicious GitHub repository can execute JavaScript in the Anonymous GitHub origin when a user anonymizes it or views its content\n2. **Account Takeover**: Steal authentication tokens and session cookies\n3. **Data Exfiltration**: Access other users' anonymization configurations and private repository data via `/api/user` and `/api/repo/list`\n\n### Proof of Concept\n\n![poc-xss-anonymous-github](https://github.com/user-attachments/assets/c1bf3ed9-4e1e-4c8e-87f0-782a8d5f6ead)\n\n1. Create a GitHub repository with a malicious `README.md`:\n\n```markdown\n# Innocent README\n\u003cimg src=x onerror=\"alert(document.domain)\"\u003e\n```\n\n2. On Anonymous GitHub, enter the malicious repository URL to anonymize it\n3. The XSS executes immediately when the README preview is rendered on the anonymize page\n\n### Remediation\n\n1. Sanitize markdown output with DOMPurify before rendering (the dependency already exists but is unused)\n2. Serve HTML files with `Content-Disposition: attachment` or in a sandboxed iframe on a separate origin\n3. Replace `$sce.trustAsHtml()` with proper `ngSanitize` usage\n4. HTML-escape filenames and paths in directory listing templates\n5. Add Content Security Policy headers\n\n### Credits\n\nZhengyu Liu, Jingcheng Yang","status":"published","published":"2026-05-05T18:28:32Z","modified":"2026-05-05T18:47:42.324391Z","cvss":[{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N","level":"high"}],"cwes":["CWE-79","CWE-80"],"updated_at":"2026-11-02T15:46:47.119724Z"}]}}
