devansh

Hacking Better-Hub

Table of Contents


Vulnerabilities

# Vulnerability Severity
01 Unsanitized README → XSS 🟠 HIGH
02 Issue Description → XSS 🟠 HIGH
03 Stored XSS in PR Bodies 🟠 HIGH
04 Stored XSS in PR Comments 🟠 HIGH
05 Reflected XSS via SVG Image Proxy 🟠 HIGH
06 Large-File XSS (>200 KB) 🟠 HIGH
07 Cache Deception — Private File Access 🔴 CRITICAL
08 Authz Bypass via Issue Cache 🟠 HIGH
09 Private Repo Prompt Leak 🟠 HIGH
10 GitHub OAuth Token Leaked to Client 🟡 Low (could be High if chained with XSS)
11 Open Redirect via Query Parameter 🟡 Low

What is Better-Hub?

Better-Hub (better-hub.com) is an alternative GitHub frontend — a richer, more opinionated UI layer built on Next.js that sits on top of the GitHub API. It lets developers browse repositories, view issues, pull requests, code blobs, and repository prompts, while authenticating via GitHub OAuth.

Because Better-Hub mirrors GitHub content inside its own origin, any unsanitized rendering of user-controlled data becomes significantly more dangerous than it would be on a static page — it has access to session tokens, OAuth credentials, and the authenticated GitHub API. That attack surface is exactly what I set out to explore.


The Vulnerabilities

01. Unsanitized README Leads to XSS

Description

The repository README is fetched from GitHub, piped through renderMarkdownToHtml with allowDangerousHtml and rehypeRaw enabled — with zero sanitization — then stored in the readmeHtml state and rendered via dangerouslySetInnerHTML in repo-overview.tsx. Because the README is entirely attacker-controlled, any repository owner can embed arbitrary JavaScript that executes in every viewer's browser on better-hub.com.

Steps to Reproduce

  1. Create a GitHub repository with the following content in README.md:
    "><img src=x onerror=prompt(1)>
    
  2. View the repository at https://www.better-hub.com/<user>/<repo> and observe the XSS popup.

Impact

Session hijacking via cookie theft, credential exfiltration, and full client-side code execution in the context of better-hub.com. Chains powerfully with the GitHub OAuth token leak (see vuln #10).


02. Unsanitized Markdown in Issue Description Leads to XSS

Description

Issue descriptions are rendered with the same vulnerable pipeline: renderMarkdownToHtml with raw HTML allowed and no sanitization. The resulting bodyHtml is inserted directly via dangerouslySetInnerHTML inside the IssueConversation thread entry component, meaning a malicious issue body executes arbitrary script for every person who views it on Better-Hub.

Steps to Reproduce

  1. Create a GitHub issue with the following in the body:
    "><img src=x onerror=prompt(1)>
    
  2. Navigate to the issue via https://www.better-hub.com/<user>/<repo>/issues/<num> to trigger the payload.

Impact

Arbitrary JavaScript execution for anyone viewing the issue through Better-Hub. Can be used for session hijacking, phishing overlays, or CSRF-bypass attacks.


03. Stored XSS via Unsafe Markdown Rendering in PR Bodies

Description

Pull request bodies are fetched from GitHub and processed through renderMarkdownToHtml with allowDangerousHtml/rehypeRaw and no sanitization pass, then rendered unsafely. An attacker opening a PR with an HTML payload in the body causes XSS to fire for every viewer of that PR on Better-Hub.

Steps to Reproduce

  1. Open a pull request whose body contains:
    "><img src=x onerror=prompt(5)>
    
  2. View the PR through Better-Hub to observe the XSS popup.

Impact

Stored XSS affecting all viewers of the PR. Particularly impactful in collaborative projects where multiple team members review PRs.


04. Stored XSS via Unsafe Markdown Rendering in PR Comments

Description

The same unsanitized renderMarkdownToHtml pipeline applies to PR comments. Any GitHub user who can comment on a PR can inject a stored XSS payload that fires for every Better-Hub viewer of that conversation thread.

Steps to Reproduce

  1. Post a PR comment containing:
    "><img src=x onerror=prompt(8)>
    
  2. View the comment thread via Better-Hub to trigger the XSS.

Impact

A single malicious commenter can compromise every reviewer's session on the platform.


05. Reflected XSS via SVG Image Proxy

Description

The /api/github-image endpoint proxies GitHub repository content and determines the Content-Type from the file extension in the path query parameter. For .svg files it sets Content-Type: image/svg+xml and serves the content inline (no Content-Disposition: attachment). An attacker can upload a JavaScript-bearing SVG to any GitHub repo and share a link to the proxy endpoint — the victim's browser executes the script within better-hub.com's origin.

Steps to Reproduce

  1. Create an SVG file in a public GitHub repo with content:
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 124 124">
      <script type="text/javascript">
        alert(0x539);
      </script>
    </svg>
    
  2. Direct the victim to:
    https://www.better-hub.com/api/github-image?owner=<user>&repo=<repo>&path=xss.svg&ref=main
    

Impact

Reflected XSS with a shareable, social-engineered URL. No interaction with a real repository page is needed — just clicking a link is sufficient. Easily chained with the OAuth token leak for account takeover.


06. Unescaped Large-File XSS for Files Over 200 KB

Description

When viewing code files larger than 200 KB, the application hits a fallback render path in apps/web/src/components/repo/code-viewer-client.tsx that outputs raw file content via dangerouslySetInnerHTML without any escaping. An attacker can host a file exceeding the 200 KB threshold containing an XSS payload — anyone browsing that file on Better-Hub gets the payload executed.

Steps to Reproduce

  1. Create a file named test.html containing the payload, padded to exceed 200 KB:
    "><img src=x onerror=prompt(31337)>
    <!-- pad to >200kb -->
    
  2. Browse to the file on Better-Hub at /blob/main/test.html. The XSS fires immediately.

Impact

Any repository owner can silently weaponize a large file. Because code review is often done on Better-Hub, this creates a highly plausible attack vector against developers reviewing contributions.


07.Authorization Bypass via Shared File Cache

Description

The readLocalFirstGitData function reads file content from a shared Redis cache. Cache entries are keyed by repository path alone — not by requesting user. The file_content field is marked as shareable, so once any authorized user views a private file through the /api/github-image handler or the blob page, its contents are written to Redis under a path-only key. Any subsequent request for the same path — from any user, authenticated or not — is served directly from cache, completely bypassing GitHub's permission checks.

Steps to Reproduce

  1. Create a private repository and add a file called secret.txt.
  2. As the repository owner, navigate to the following URL to populate the cache:
    https://www.better-hub.com/api/github-image?owner=<user>&repo=<private-repo>&path=secret.txt&ref=main
    
  3. Open the same URL in an incognito window or as a completely different user.
  4. The private file content is served — no authorization required.

Impact

Complete confidentiality breach of private repositories. Any file that has ever been viewed by an authorized user is permanently exposed to unauthenticated requests. This includes source code, secrets in config files, private keys, and any other sensitive repository content.


08. Authorization Bypass via Improper Cache Management on Issues

Description

A similar cache-keying problem affects the issue page. When an authorized user views a private repo issue on Better-Hub, the issue's full content is cached and later embedded in Open Graph meta properties of the page HTML. A user who lacks repository access — and sees the "Unable to load repository" error — can still read the issue content by inspecting the page source, where it leaks in the og:* meta tags served from cache.

Steps to Reproduce

  1. Create a private repo and create an issue with a sensitive body.
  2. Open the issue as an authorized user: https://www.better-hub.com/<user>/<repo>/issues/<num>
  3. Open the same URL in a different session (no repo access).
  4. While the access-error UI is shown, view the page source — issue details appear in the <meta property="og:*"> tags.

Impact

Private issue contents — potentially including bug reports, credentials in descriptions, or internal discussion — are accessible to any unauthenticated party who knows or guesses the URL.


09. Authorization Bypass — Accessing Prompts on Private Repos

Description

Better-Hub exposes a Prompts feature tied to repositories. For private repositories, the prompt data is included in the server-rendered page source even when the requestor does not have repository access. The error UI correctly shows "Unable to load repository," but the prompt content is already serialized into the HTML delivered to the browser.

Steps to Reproduce

  1. Create a private repository and create a prompt in it.
  2. Open the prompt URL as an unauthorized user:
    https://www.better-hub.com/<user>/<repo>/prompts/<id>
    
  3. View the page source — prompt details are present in the HTML despite the access-denied UI.

Impact

Private AI prompts — which may contain internal instructions, proprietary workflows, or system prompt secrets — leak to unauthenticated users.


10. GitHub OAuth Access Token Leaked to Client-Side JavaScript

Description

getServerSession returns a session object that includes githubUser.accessToken. This session object is passed as props directly to client components (AppNavbar, DashboardContent, etc.). Next.js serializes component props and embeds them in the page HTML for hydration, meaning the raw GitHub access token is present in the page source and accessible to any JavaScript running on the page — including scripts injected via any of the XSS vulnerabilities above.

Root Cause

The fix is straightforward: strip accessToken from the session object before passing it as props to client components. Token usage should remain server-side only.

Impact

When chained with any XSS in this report, an attacker can exfiltrate the victim's GitHub OAuth token and make arbitrary GitHub API calls on their behalf — reading private repos, writing code, managing organizations, and more. This elevates every XSS in this report from session hijacking to full GitHub account takeover.


11. Open Redirect via Unvalidated Query Parameter

Description

The home page redirects authenticated users to the destination specified in the ?redirect= query parameter with no validation or allow-listing. An attacker can craft a login link that silently redirects the victim to an attacker-controlled domain immediately after they authenticate.

Steps to Reproduce

  1. Log in to Better-Hub with GitHub credentials.
  2. Navigate to:
    https://www.better-hub.com/?redirect=https://example.com
    
  3. You are immediately redirected to example.com.

Impact

Phishing attacks exploiting the trusted better-hub.com domain. Can be combined with OAuth token flows for session fixation attacks, or used to redirect users to convincing fake login pages post-authentication.


Disclosure Timeline

All issues were reported directly to Better-Hub team. The team was responsive and attempted rapid remediation.

Date Event
Feb 25–26, 2026 Initial reports sent covering all XSS variants, cache deception, authorization bypasses, OAuth token leak, and open redirect.
Feb 26, 2026 — 8:15 PM Bereket confirmed receipt and stated "everything should be fixed." Mentioned plans to launch a bug bounty program.
Feb 26, 2026 Re-tested cache deception attack — still reproducible. Reported ongoing issue.
Feb 27, 2026 — 11:40 PM Bereket stated the cache issue "should have been fixed as of yesterday."
Pending Awaiting CVE assignment and public advisory on the Better-Hub repository.