Date: 2026-05-14
Reviewer: /owasp (Claude Opus 4.7)
Scope: Entire src/ tree, manifest.json, .github/workflows/
Mode: Review (no exploitation, read-only)
Auto Rebaser is a browser-only MV3 extension. Trust boundaries:
api.github.com / configured GHES. Holds an OAuth user-to-server token (GitHub App device flow, with refresh) or a legacy PAT in chrome.storage.local.chrome.runtime message channel.enterpriseHost becomes a fetch origin and an Authorization-header recipient.chrome.storage.local is unencrypted at rest.No content scripts, no LLM calls, no server-side component, no third-party JS at runtime (React + bundled). No eval, innerHTML, or dangerouslySetInnerHTML found.
Skipped — Semgrep/gitleaks/trivy not installed locally; will be addressed by SEC-8 below.
chrome.runtime.onMessage — Mediumsrc/background/messages.ts:78-80sender. Any future content script, externally-connectable extension, or compromised in-process page could invoke START_DEVICE_FLOW, POLL_NOW, CANCEL_DEVICE_FLOW. Today only the popup speaks to the SW, but defense-in-depth is missing.sender.id === chrome.runtime.id and sender.url?.startsWith(chrome.runtime.getURL('')) before dispatching; return {ok:false, error:'UNAUTHORIZED_SENDER'} otherwise. Same fix for any other onMessage listeners.enterpriseHost interpolation lacks defense-in-depth — Mediumsrc/core/host-config.ts:14,18,22; validateHost at :60.https://${host} and https://${host}/api/v3 are built from user input. validateHost allows [a-z0-9.-]+ but does not reject leading/trailing - or ., consecutive dots, length > 253, or single-label hosts. If chrome.storage.local is tampered with (devtools, another extension with management perm, malware on disk) the validator is bypassed and the access token is sent to an attacker-controlled origin.validateHost: reject .., leading/trailing . or -, length > 253, labels > 63 chars, require at least one dot for non-localhost.new URL(url).hostname matches api.github.com OR equals settings.enterpriseHost (read fresh, not interpolated). Refuse to send Authorization otherwise..github/workflows/ci.yml — only runs typecheck/test/build/e2e.npm audit, osv-scanner, gitleaks, or dependency-review action. A vulnerable transitive (vite/react ecosystem) or a leaked token in a future commit would not be caught.security.yml workflow running npm audit --audit-level=high, osv-scanner, gitleaks detect --no-git, and the official actions/dependency-review-action@v4 on PRs. The /harden-project skill ships this scaffold.manifest.json."content_security_policy": { "extension_pages": "script-src 'self'; object-src 'self'; base-uri 'self'" } (and the firefox manifest variant). Confirm popup still renders.chrome.storage.local (unencrypted at rest) — Lowsrc/core/auth-store.ts, src/core/storage/multi-account.ts.chrome.storage.session (in-memory, cleared on browser restart) and only persist the refresh token in local. SW eviction triggers a refresh on next request — acceptable. Write an ADR capturing the trade-off.Authorization header sent before any host allowlist re-check — Lowsrc/github/http.ts:54, src/github/http-extra.ts:30, src/core/auth-store.ts:71, src/background/auth-device-flow-runner.ts:76.getApiBase() and attach Bearer ${token} without an inline host check. Fixed by SEC-2’s request-site assertion, but listed separately so the fix is verified across all four call sites.assertGithubOrigin(url) called inside request() / requestText() / fetchLoginForToken() / device-flow user fetch before any fetch(url, …Authorization…)./security-review and /security-gate exist as harness skills but are not wired into CI. Manual-only review will drift.npm run typecheck && npm test && npm run build && npx playwright test and, when secrets allow, a Claude Code action calling /security-gate on PRs touching src/core/auth*, src/github/http*, manifest*.json, .github/workflows/**.eval, new Function, innerHTML, dangerouslySetInnerHTML, document.write anywhere in src/.chrome.notifications.create (plain text, not HTML).expiresAt — no infinite loop.client_secret is held client-side (device flow does not require one).optional_host_permissions: ["https://*/*"] is broad in manifest but narrowed at request time to https://${host}/* (SettingsView.requestHostPermission).console.log/console.error call we audited.Added to docs/superpowers/BACKLOG.md §5 — see SEC-1 through SEC-8.