Skip to content

fix: hygiene + security follow-ups (closes #33, #34, #35, #39)#40

Merged
paperhurts merged 14 commits into
mainfrom
feat/hygiene-and-hardening
May 27, 2026
Merged

fix: hygiene + security follow-ups (closes #33, #34, #35, #39)#40
paperhurts merged 14 commits into
mainfrom
feat/hygiene-and-hardening

Conversation

@paperhurts
Copy link
Copy Markdown
Owner

Summary

Closes #33, #34, #35, #39.

Test plan

  • Full monorepo green: 286 unit + component tests, typecheck clean, all 5 packages build.
  • Manual smoke: `pnpm e2e` runs against real Chromium (the corrected `pree2e` hook now wires HTML copy + vite build).
  • Manual smoke: load both extensions; web UI's CSP doesn't break dev or built preview; Sign out clears settings + returns to /setup.
  • Manual smoke: edit a remote `bookmarks.json` to add a `javascript:` entry → the extension's poll skips it (console.warn) and never creates the bookmark in the native tree.

Implementation notes

  • Plan: `docs/superpowers/plans/2026-05-26-gitmarks-hygiene-and-hardening.md` (8 tasks, subagent-driven).
  • Inline fix during review: the original `pretest:e2e` hook would only fire before a script literally named `test:e2e` (which didn't exist). Renamed to `pree2e` + added `pree2e:headed` so `pnpm e2e` reliably copies HTML + builds before Playwright runs.

🤖 Generated with Claude Code

paperhurts and others added 14 commits May 26, 2026 13:21
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…fill namespace; drop @types/chrome

Replaces all type-position chrome.bookmarks.* and chrome.runtime.* references
in extension-shared src and tests with Bookmarks.*/Runtime.* from
webextension-polyfill. Adds test/globals.d.ts to declare the chrome global
for the test stub without @types/chrome. Drops @types/chrome devDep and the
"chrome" types entry from tsconfig.json across extension-shared, -chrome, and
-firefox. Zero runtime changes; 283 tests still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tion calls

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ifests

Adds CSP meta tag to the web UI (connect-src restricted to api.github.com,
no unsafe-eval, frame-ancestors none) and pins content_security_policy in
both Chrome and Firefox MV3 manifests (script-src/object-src 'self' +
connect-src https://api.github.com).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Guard both the create and edit paths in applyRemoteChanges against
javascript:, data:, vbscript: and other unsafe URL schemes using
isSafeBookmarkUrl from @gitmarks/core. Tombstones are exempted so
soft-deletes still propagate even if the stored URL is malformed.
applyRemoteEdit also gains a belt-and-suspenders early-return.
Adds 2 new tests (extension-shared now 100).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Update CLAUDE.md and README.md with current test counts (core: 77,
extension-shared: 100, web: 109, total: 286) and add four new
load-bearing invariant bullets documenting the URL safety filter,
remote file Zod re-validation, tag color guard, and CSP posture
established by the hygiene-and-hardening PR.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rop dead inner guard

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…'self' in CSP, copy-html per-file check, sign-out disabled during in-flight writes
@paperhurts
Copy link
Copy Markdown
Owner Author

Review-driven fixes — 13 of 15 findings addressed

8243d32 — URL safety consistency (findings #1, #2, #3, #8, #9, #10):

  • `listeners.ts applyBatch`: filter create + update events with unsafe URLs (closes the SW write path bypass).
  • `reconcile.ts`: filter `remoteByUrl` construction (kills phantom idMap entries) + filter `localOnly` upload loop (closes the cold-start upload bypass).
  • `apply-remote.ts`: removed the unreachable inner `isSafeBookmarkUrl` guard from `applyRemoteEdit`; wrapped tombstone-branch `suppress(bm.url)` in the URL check.
  • Test "skips a remote edit with unsafe URL scheme" renamed + assertion updated to reflect what it actually tests; `browser.bookmarks.get` mock setup removed.
  • +8 new tests covering each filter.

ad0c065 — CSP + test type narrowing (findings #4, #5, #7):

  • `packages/web/index.html`: removed the unenforceable `frame-ancestors 'none'` directive (meta CSP can't enforce it per CSP3) and moved CSP injection to a Vite `transformIndexHtml` plugin scoped to `apply: 'build'`. Dev mode now serves no CSP meta tag, so Vite HMR works again.
  • `extension-shared/test/globals.d.ts`: narrowed `var browser: typeof Browser` to a runtime-shape interface (`Browser.Bookmarks.Static` etc.) so future test code can no longer typecheck against capitalized namespace re-exports that crash at runtime.

503a0e3 — misc (findings #6, #11, #12, #15):

  • `listeners.ts` onChanged: added a load-bearing WHY comment explaining that `title === undefined` is needed at runtime despite the polyfill type declaring it required, and cast accordingly.
  • Both extension manifests: `connect-src 'self' https://api.github.com\` (added `'self'` so future extension-internal fetches aren't blocked).
  • Both `copy-html.mjs` scripts: per-file existence check with informative error.
  • `Layout` got a `busy?: boolean` prop that disables the Sign out button while a write is in flight; all three pages thread their `writing` state through.

Deferred (DX only, not bugs):

Test posture: 294 unit + component tests (77 core + 108 ext-shared + 109 web), typecheck + build clean on all 5 packages.

Verified REFUTED during review (not bugs):

  • The `pree2e:headed` colon-script hook DOES fire (empirically tested).
  • Chrome MV3 DOES enforce `connect-src` in `extension_pages` CSP (per official Chrome docs).

@paperhurts
Copy link
Copy Markdown
Owner Author

Browser smoke test — all checks pass

Playwright e2e (`pnpm --filter @gitmarks/extension-chrome e2e`): 4 passed, 2 skipped (same posture as before the PR — no regressions). The renamed `pree2e` hook fired correctly: `[chrome] copied popup.html + options.html from extension-shared` appeared before vite build, confirming the colon-script-hook fix landed.

Web UI (`pnpm --filter @gitmarks/web build && vite preview` against http://localhost:4173/):

  • Build-only CSP: opened the preview, confirmed the production `<meta http-equiv="Content-Security-Policy">` tag is injected by the Vite plugin and includes `connect-src https://api.github.com\`, with `frame-ancestors` correctly absent.
  • Setup gate: navigating to `/` redirected to `#/setup` (RequireSettings working).
  • List page: with settings + mocked GitHub fetch, the list rendered 3 bookmarks. Hacker News and the arXiv paper rendered as anchor links; the malicious `javascript:alert(1)` entry rendered as a non-clickable `` (italic, magenta) with `title="unsafe URL scheme — not clickable"` — the BookmarkRow URL safety guard works as designed.
  • Sign out flow: clicked Sign out → URL changed to `#/setup`, "Set up gitmarks" heading visible, `localStorage.getItem('gitmarks:web:settings')` returned `null`.
  • ✅ No CSP violations in the console during the entire flow.

Test plan now:

  • Full monorepo green: 290 unit + component tests, typecheck + build clean on all 5 packages.
  • `pnpm e2e` runs against real Chromium (4/6 passing, 2 long-skipped).
  • Web UI's CSP doesn't break dev or built preview; Sign out clears settings + returns to /setup.
  • (Unit-tested) edit a remote `bookmarks.json` to add a `javascript:` entry → the extension's poll skips it and never creates the bookmark.

Three smoke-test screenshots delivered out-of-band: setup page → list page (note the italic non-clickable EVIL row) → post-sign-out setup page.

@paperhurts paperhurts merged commit ed7c4e2 into main May 27, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HTML duplication across extension shells (Chrome + Firefox + shared)

1 participant