Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
pasteguard-PoC — PoC — cross-origin proxy abuse of configured provider API keys in PasteGuard (GHSA-q94x-p9rc-q89f, CVE-2026-86998, CVSS 7.6). | Kitploit
Tools/GitHubGitHub/squeeze440/pasteguard-poc
Vulnerability AnalysisExploitationWeb SecurityPenetration TestingPapers & ResearchAPI Security
GitHubsqueeze440/pasteguard-poc

pasteguard-PoC

PoC — cross-origin proxy abuse of configured provider API keys in PasteGuard (GHSA-q94x-p9rc-q89f, CVE-2026-86998, CVSS 7.6).

View Repository
7 days agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

PasteGuard: security advisory

CVE status: requested, pending assignment. This finding is published as GHSA-q94x-p9rc-q89f. On CVE assignment this repository is renamed CVE-YYYY-NNNNN-pasteguard-PoC and this banner is replaced with the CVE link.

ResearcherDostxodjayev Abdullox (@squeeze440)
AdvisoryGHSA-q94x-p9rc-q89f
CVSS 3.17.6 (High)
WeaknessCWE-352, CWE-942

Summary: Missing CORS/CSRF protection on the LLM-proxy routes in PasteGuard 0.9.1 allows a remote attacker (any website the operator's browser visits, or any host on the local network) to trigger authenticated requests to the operator's configured OpenAI/Anthropic API using PasteGuard's server-side fallback API key, and read the response, via a cross-origin fetch() to /openai/v1/chat/completions (and sibling /anthropic/v1/messages).

Product: PasteGuard (github.com/sgasser/pasteguard)

Tested Version: commit 100718191499c52934f3b2ccca72ca16373fb765 (2026-07-30), package.json version 0.9.1

Estimated CVSS v3.1: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:H — 7.6 (High)

  • UI:R — the operator must have their browser open a page (any page, on any site) while PasteGuard is running; no other interaction is needed.
  • C:L / I:L — the attacker's own JS reads only the response to its own attacker-chosen prompt (not the operator's past conversations/dashboard data, which is correctly excluded from CORS), but this still confirms/uses a live funded account and pollutes the account's usage/audit trail under the operator's identity.
  • A:H — the realistic, unbounded impact: an attacker page can loop this request indefinitely, driving real billed usage against the operator's provider account/quota (financial drain, possible rate-limit exhaustion or provider-side abuse flags) with no rate limiting or origin check in the way.
  • S:U — impact stays within the same trust boundary (the proxy's own configured credential is what gets used); no separate security authority is crossed.

Details:

PasteGuard's HTTP layer applies a permissive, wildcard CORS policy to every route except the dashboard:

  • src/index.ts:40-45 — const corsMiddleware = cors(); (Hono's cors() with no options defaults to Access-Control-Allow-Origin: *) is applied via app.use("*", ...) to everything, with an explicit carve-out only for /dashboard and /dashboard/*. The comment directly above (src/index.ts:34-39) shows the author reasoned carefully about dashboard CORS exposure but did not extend the same reasoning to the proxy routes below it.
  • src/config.ts:153 — host: z.string().default("0.0.0.0"), confirmed live in config.example.yaml:13. The proxy listens on all interfaces by default, not just loopback, so this is reachable from the LAN too, not only from the operator's own browser.
  • src/providers/openai/client.ts:48-53:
    root@kitploit:~
    // Use client's auth header if provided, otherwise fall back to config
    if (authHeader) {
      headers.Authorization = authHeader;
    } else if (config.api_key) {
      headers.Authorization = `Bearer ${config.api_key}`;
    }
    
    If the incoming request carries no Authorization header, PasteGuard silently attaches its own server-held providers.openai.api_key to the outbound request. config.example.yaml:22-24 documents this as an intentional convenience feature ("Optional fallback if client doesn't send auth header") for the "Apps & APIs" use case.
  • src/providers/anthropic/client.ts:37-61 implements the identical fallback pattern for /anthropic/v1/messages (x-api-key/Authorization fallback to config.api_key) — confirmed as a sibling instance of the same root cause, not independently re-verified end-to-end (see PoC scope below).

Because the wildcard CORS policy sits in front of routes that hold this fallback credential, any origin can (1) make the browser send the request with no Authorization header, causing the server to attach its own real API key, and (2) read the JSON response, since Access-Control-Allow-Origin: * is present. No Origin/Referer check or CSRF token guards these routes.

Proof of Concept (dynamically confirmed, not just statically traced):

  1. Configured config.yaml with providers.openai.base_url: http://127.0.0.1:9091 (mock upstream) and providers.openai.api_key: "sk-VICTIM-SECRET-DO-NOT-LEAK-12345", pii_detection.enabled: false (mocked detector /health only, to avoid needing the full GLiNER model service), secrets_detection.enabled: false. Started PasteGuard with bun run src/index.ts, confirmed /health → 200.
  2. Started a mock upstream (mock_upstream.py) on 127.0.0.1:9091 that logs whatever Authorization header it receives.
  3. Served a real attacker page from a genuinely distinct origin, http://127.0.0.2:8001/attack.html (different literal loopback IP, per Chrome's site-isolation rules — not a same-origin localhost test), via python3 -m http.server 8001 --bind 127.0.0.2. The page's only action on load:
    root@kitploit:~
    fetch("http://127.0.0.1:3000/openai/v1/chat/completions", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ model: "gpt-4o-mini", messages: [{ role: "user", content: "cross-origin drive-by call, attacker supplied NO api key" }] })
    }).then(r => r.json()).then(data => { /* render on page */ });
    
  4. Drove a real Chrome instance (Chrome DevTools Protocol) to http://127.0.0.2:8001/attack.html. Confirmed via DevTools network inspection:
    • Request: origin: http://127.0.0.2:8001, sec-fetch-site: cross-site, no Authorization header sent by the attacker page.
    • Response: access-control-allow-origin: *, HTTP 200, JSON body fully readable by the attacker page's JS.
    • Page rendered: "CROSS-ORIGIN READ SUCCEEDED. Response body visible to attacker JS: ..." — screenshot: ../evidence/cross_origin_csrf_success.png.
    • Mock upstream log for that exact request: RECEIVED AUTH HEADER: Bearer sk-VICTIM-SECRET-DO-NOT-LEAK-12345 — proving PasteGuard attached the operator's real configured key server-side, without the attacker page ever knowing or supplying it.

Raw evidence: ~/engagements/pasteguard/evidence/cross_origin_csrf_success.png, ~/engagements/pasteguard/evidence/mock_upstream_log.txt.

The /anthropic/v1/messages route was confirmed statically (file:line above) to share the identical fallback + wildcard-CORS pattern but was not separately re-run through the live browser PoC, in the interest of the session's depth-over-breadth/5-minute-rule guidance once the root cause was already confirmed on /openai.

Impact: Any website the PasteGuard operator's browser visits (malicious ad, compromised site, or an attacker on the same LAN given the default 0.0.0.0 bind) can silently drive unlimited billed requests through the operator's own OpenAI/Anthropic account via their local PasteGuard instance, with no user interaction beyond having the tab open and no way for the operator to notice short of watching their provider billing dashboard. This is a direct financial-abuse / quota-exhaustion vector, and secondarily pollutes the operator's provider account usage/audit trail with attacker-chosen content.

Weaknesses:

  • CWE-352: Cross-Site Request Forgery
  • CWE-942: Permissive Cross-domain Policy with Untrusted Domains
  • CWE-798: Use of Hard-coded Credentials (the server-side fallback key is transparently reusable by any caller that omits its own auth) — listed as a contributing factor, not the primary weakness

Remediation (suggestions):

  • Do not apply the wildcard cors() middleware to /openai, /anthropic, /codex (and /api/mask if it's ever extended to use server-held credentials) when a fallback api_key is configured. At minimum, make the allowed origins for these routes explicit/configurable (e.g. only the browser extension's known origin), defaulting to no cross-origin access rather than *.
  • Add an Origin/Referer check (or a lightweight shared local token the legitimate browser extension attaches) on the fallback-key code path specifically, mirroring the reasoning already applied to /dashboard in src/index.ts.
  • Change server.host default from 0.0.0.0 to 127.0.0.1 in src/config.ts:153 and config.example.yaml:13, requiring an explicit opt-in to bind on all interfaces.

Credit: Dostxodjayev Abdullox

Reporting Channel: No SECURITY.md exists in the repository (confirmed via gh api repos/sgasser/pasteguard/contents/SECURITY.md → 404, re-checked at audit time). No prior published security advisories either (gh api repos/sgasser/pasteguard/security-advisories → []), so this does not appear to be a duplicate of a previously disclosed issue. Recommended channel: GitHub's default private vulnerability reporting flow at https://github.com/sgasser/pasteguard/security/advisories/new.

Download Tool