Conversation
Address three findings from a security audit of the branch: - XSS (stored): user link/source URLs were rendered straight onto <a :href> with only a trim(). sanitizeLinks() now scheme-validates via new URL() (http/https/mailto only), case-study `sources` are routed through it (they bypassed it entirely), and a safeUrl() render guard backs the three link render sites as defense-in-depth for any pre-existing data. - Email-PIN abuse: /_auth/email-pin/send was unauthenticated with no HTTP rate limit, allowing a mail-flood/spam relay across arbitrary addresses. Added per-IP + per-email limits on send, and per-IP + per-email limits on verify so PIN brute force is bounded at the HTTP layer, not just by the non-atomic per-code attempt counter. - Moderation prompt injection: user content is now fenced in <submission> tags with a system caveat telling the model to treat it as untrusted data; stripFenceTokens() prevents forging the boundary; model-suggested new tags are bounded (<=3, <=40 chars, no empty slugs) so injection can't pollute the global taxonomy. Bumped issue.moderate / case-study.moderate prompt versions and updated the golden snapshots. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…aseStudy The diff-gate flagged createCaseStudy as an introduced complexity finding only because this PR touches it to route `sources` through sanitizeLinks (net-neutral to its pre-existing critical complexity). Scoped fallow-ignore with rationale; the finding is legacy debt, not introduced by this change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…it-fixes 🔒 Security: harden links (XSS), email-PIN rate limits, and AI moderation injection
🧹 Code quality
fallow audit outputupdated for |
- Move the parent "Case study of" callout below the title and badges, matching how issues render (id → title → badges → parent). - Hide the owner/collaborators panel behind a dedicated Contributors page and the change log behind a History page, reached via quiet meta links at the bottom of the overview — the same pattern issues and solutions use. Both are now nested routes under /case-study/[id]. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MFHDAHHvfjqmUf7TycaJJ
- Extract the outcome/scale badge label+variant maps — previously copy-pasted across the case-study page, case-study card, tree node, and issue overview — into a single app/utils/case-study.ts. - Extract the shared "Contributors · History" overview footer into a NodeMetaLinks component, used by both the issue and case-study overviews, and the meta-page "← back" link into NodeBackLink. No visual change; purely removes duplication. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MFHDAHHvfjqmUf7TycaJJ
- Pin vue-tsc (2.2.10) in devDependencies. CI runs `bunx vue-tsc`, which otherwise resolves vue-tsc@latest (v3) — incompatible with the pinned TypeScript on Node 24 (ERR_PACKAGE_PATH_NOT_EXPORTED './lib/tsc'), failing the required typecheck job repo-wide. A local pin makes bunx use a working version. - Apply vize formatting to the new/changed case-study files. - Extract case-study date-range + cost formatting into small pure helpers in app/utils/case-study.ts (shared, testable, lower per-fn complexity). - Extract the near-identical Sources/Links lists into CaseStudyLinkCard. - Flatten history.vue's template (derive viewerId/revisionCount in script). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MFHDAHHvfjqmUf7TycaJJ
GitHub Actions did not schedule a run for the previous commit; empty commit to fire the pull_request:synchronize workflow. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MFHDAHHvfjqmUf7TycaJJ
…ribution Resolves the conflict in app/pages/case-study/[id].vue (the refactor turned it into a <NuxtPage /> shell, so staging's safeUrl() hardening of the sources/links hrefs from #94 no longer applied there). Kept the shell and re-applied safeUrl() in the extracted CaseStudyLinkCard component so the security fix survives. Also scopes fallow-ignore comments on the shell + Overview templates: the refactor splits one monolithic template into two simpler ones (net complexity drop) but the diff-gate re-attributes each touched template as introduced — same pattern as createCaseStudy (bb3550c). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EzhVToTww4v85fHmuWyPEh
…n id
- Remove provide('caseStudyParent') — no child route injects it (the parent
callout is rendered in the shell itself).
- Key the pending-banner revisions fetch on the case-study id so client
navigation between two studies can't briefly surface the previous count.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EzhVToTww4v85fHmuWyPEh
…p257al Refactor case study page into nested routes with Overview/History/Contributors
…tter prefs (#97) * ✨ feat: catalog navigation rework, wanted skills, onboarding + newsletter prefs Navigation and discovery: - Rework home page: new hero, linked catalog stat line, catalog-wide full-text search (/api/search/quick) with grouped results, topic quick-nav chips, explore-card grid into every section - New browse pages: /issues (tag + SDG + sort filters), /solutions (status filter), /case-studies (outcome/scale/semantic filters), /tags (usage-ranked topic index), /map (fullscreen map of all geolocated content with layer toggles) - New endpoints: /api/stats, /api/map, /api/solutions, /api/search/quick; sdg filter on /api/issues; usage counts on /api/tags and /api/sdgs - Minimal redesign of case-study cards; solution cards drop irrelevant sub-issue/solution chips; sitemap + SEO for new pages - Mobile responsiveness pass across public pages Community features: - Wanted skills on issues/solutions: schema + unique index, CRUD endpoints, node-page section with social share, contribute card - Contribute page rework with always-on ways to help - Onboarding flow (/onboarding): skills, free-text interests with embedding-based topic preview (/api/tags/similar), newsletter opt-in with frequency + content preferences - user_interests (vector embeddings + HNSW index), newsletter_prefs, users.onboarded_at; settings sections for interests + newsletter Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * 🐛 fix: typecheck + formatting for CI Typecheck (vue-tsc): - Widen useUmami track() data type to accept boolean | undefined so event props like { enabled, on, newsletter } and conditional { tag: x || undefined } type-check (NewsletterPrefs, map, onboarding, issues pages). - contribute.vue: type help-label counts as Record<string, number> and guard LABEL_META[activeLabel]?.hint under noUncheckedIndexedAccess. Formatting (vize fmt): reformat files flagged by format:check (admin logs handler wrap + drizzle migration meta trailing newlines). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * 🐛 fix: address PR #97 review findings - Escape user-derived values in revision notification emails (shared escapeHtml util, also reused by the OAuth consent page) - Guard the revision approve compensation and make post-apply snapshot/audit writes loud-but-best-effort (never revert an applied revision to pending — that would allow a double apply) - Exclude rejected case studies from the me/overview counter - Add missing DB CHECK constraints for revisions.ai_verdict, revisions.decided_by_role, and newsletter_prefs.frequency - /api/case-studies now returns { items, degraded } so clients can tell when a semantic query fell back to recency; surface it on the page - Remove forbidden data-umami-event attributes; use useUmami() instead of the bare umami global in appeal handlers - Clear debounce timers on unmount; skip empty-query quick search on homepage first paint - Fix tests: assert the intended most_voted default sort, make similar.test.ts actually exercise the endpoint (summary, not description), and turn the stale issue-detail moderation-fields test into a privacy regression test Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ✨ feat: rework onboarding UI/UX Clickable stepper with sharp progress bars and live count chips, personalized greeting, icon tiles + ghost step numerals on cards, tap-to-fill skill examples, back navigation with direction-aware transitions, ?step= URL sync, auto-add of typed-but-unsubmitted skills on continue, and a completion panel with next-step CTAs instead of an instant redirect. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ♻️ refactor: extract shared issue list, guard, and counter helpers Deduplicate the issue/solution list queries (issue-list.ts), route-param and node-lookup guards (route-guards.ts), and parent counter updates (issue-counters.ts, shared with the moderation worker), and spread a common timestamps pair across the schema tables. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ♻️ refactor: share leaflet zoom-control styling in assets/css/leaflet.css The three map components carried identical :deep(.leaflet-control-zoom) overrides; hoist them into one global stylesheet. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ✨ feat: newsletter sending pipeline Weekly/monthly digest emails for opted-in users, sent by cron tasks (Mondays / 1st of month, 8am UTC): - newsletter-digest.ts assembles per-run sections (good news, help wanted, product updates) once and per-user sections (interest matches via pgvector, skill matches vs recent wanted skills) per recipient; empty digests are never sent - newsletter-send.ts mirrors compute:trust-scores (chunked, per-user failures isolated) with a lastSentAt column guarding cron retries - dashboard-bento email template (rounded cards on gray, Oswald caps, chips, blue pill button) with a plaintext twin; all user content escaped - HMAC-token unsubscribe endpoint that works logged-out, linked from every footer - sendEmail now works without an h3 event: the cloudflare:scheduled hook stashes the EMAIL binding for tasks Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * 💄 style: drop duplicated wordmark from homepage hero Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ♻️ refactor: dedupe shared helpers + reformat drizzle meta Eliminate all 6 fallow clone groups: - add requireEventContext/loadIssueOr404 to route-guards, shared by the 5 issue/case-study moderation handlers - extract formatRelative into app/utils/relative-time.ts (4 call sites) - extract cleanLinkRows into app/utils/link-rows.ts (6 call sites) Also reformat the two drizzle-generated migration meta files that were failing vize fmt --check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * 🔧 chore: exclude drizzle migrations dir from vize Drizzle rewrites the snapshot/journal JSON in its own format on every db:generate, so scanning them just makes a new migration fail fmt --check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release PR from staging to master.