GHSA-G485-8J3V-P6X8

Vulnerability from github – Published: 2026-05-05 18:28 – Updated: 2026-05-05 18:28
VLAI
Summary
@tdurieux/anonymous_github Vulnerable to XSS via Unsanitized GitHub Repository Content Rendering in Anonymous GitHub Origin
Details

Summary

Anonymous 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.

Details

README fetched from GitHub API

The server fetches the README via GitHub's REST API and stores the raw markdown in MongoDB:

// https://github.com/tdurieux/anonymous_github/blob/b2d77faa6c6f35ad9ae6ed46e3a3fa4681ac84c2/src/core/source/GitHubRepository.ts#L162-L174
const ghRes = await oct.repos.getReadme({
  owner: this.owner,
  repo: this.repo,
  ref: selected?.commit,
});
const readme = Buffer.from(
  ghRes.data.content,
  ghRes.data.encoding as BufferEncoding
).toString("utf-8");
selected.readme = readme;
await model.save();

It is then served to the client with no sanitization:

// https://github.com/tdurieux/anonymous_github/blob/b2d77faa6c6f35ad9ae6ed46e3a3fa4681ac84c2/src/server/routes/repository-private.ts#L254-L260
return res.send(
  await repo.readme({
    accessToken: token,
    force: req.query.force == "1",
    branch: req.query.branch as string,
  })
);

Client-side rendering via $sce.trustAsHtml() + ng-bind-html

The client fetches the raw README, parses it with renderMD() (which uses marked with sanitize: false), then bypasses AngularJS sanitization:

// https://github.com/tdurieux/anonymous_github/blob/b2d77faa6c6f35ad9ae6ed46e3a3fa4681ac84c2/public/script/app.js#L1219-L1226
const res = await $http.get(`/api/repo/${o.owner}/${o.repo}/readme`, {
  params: { force: force === true ? "1" : "0", branch: $scope.source.branch },
});
$scope.readme = res.data;
// https://github.com/tdurieux/anonymous_github/blob/b2d77faa6c6f35ad9ae6ed46e3a3fa4681ac84c2/public/script/app.js#L1339-L1343
const html = renderMD(
  $scope.anonymize_readme,
  `https://github.com/${o.owner}/${o.repo}/raw/${$scope.source.branch}/`
);
$scope.html_readme = $sce.trustAsHtml(html);  // sink: bypasses Angular XSS protection

The renderMD() function explicitly disables sanitization:

// https://github.com/tdurieux/anonymous_github/blob/b2d77faa6c6f35ad9ae6ed46e3a3fa4681ac84c2/public/script/utils.js#L165-L176
marked.setOptions({
  sanitize: false,  // HTML in markdown is preserved as-is
  // ...
});
return marked.parse(md, { renderer });

The resulting HTML is bound to the DOM via ng-bind-html, which trusts the string marked by $sce.trustAsHtml() and inserts it as innerHTML.

Impact

  1. Stored XSS: Any malicious GitHub repository can execute JavaScript in the Anonymous GitHub origin when a user anonymizes it or views its content
  2. Account Takeover: Steal authentication tokens and session cookies
  3. Data Exfiltration: Access other users' anonymization configurations and private repository data via /api/user and /api/repo/list

Proof of Concept

poc-xss-anonymous-github

  1. Create a GitHub repository with a malicious README.md:
# Innocent README
<img src=x onerror="alert(document.domain)">
  1. On Anonymous GitHub, enter the malicious repository URL to anonymize it
  2. The XSS executes immediately when the README preview is rendered on the anonymize page

Remediation

  1. Sanitize markdown output with DOMPurify before rendering (the dependency already exists but is unused)
  2. Serve HTML files with Content-Disposition: attachment or in a sandboxed iframe on a separate origin
  3. Replace $sce.trustAsHtml() with proper ngSanitize usage
  4. HTML-escape filenames and paths in directory listing templates
  5. Add Content Security Policy headers

Credits

Zhengyu Liu, Jingcheng Yang

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "@tdurieux/anonymous_github"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "2.2.0"
            },
            {
              "fixed": "2.3.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ],
      "versions": [
        "2.2.0"
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-79",
      "CWE-80"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-05-05T18:28:32Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "### Summary\n\nAnonymous GitHub fetches repository content (e.g., markdown files) from GitHub\u0027s 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\u0027s 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\u0027s 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\u0027 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",
  "id": "GHSA-g485-8j3v-p6x8",
  "modified": "2026-05-05T18:28:32Z",
  "published": "2026-05-05T18:28:32Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/tdurieux/anonymous_github/security/advisories/GHSA-g485-8j3v-p6x8"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/tdurieux/anonymous_github"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "@tdurieux/anonymous_github Vulnerable to XSS via Unsanitized GitHub Repository Content Rendering in Anonymous GitHub Origin"
}


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…

Detection rules are retrieved from Rulezet.

Loading…

Loading…