GitHub Security Advisory: GHSA-g485-8j3v-p6x8
Package: @tdurieux/anonymous_github (NPM)
Severity: HIGH (CVSS 8.1)
CWE: CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting'), CWE-80: Improper Neutralization of Script-Related HTML Tags in a Web Page (Basic XSS)
Vulnerable versions: = 2.2.0
Patched in: 2.3.0

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:

```typescript
// 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:

```typescript
// 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:

```javascript
// 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;
```

```javascript
// 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:

```javascript
// 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 ses