Skip to content

Fix four public-data and consent leaks; add repo audit and agent skills - #119

Merged
paccloud merged 13 commits into
mainfrom
claude/repo-audit-improvement-ozhlau
Sep 29, 2026
Merged

paccloud merged 13 commits into
mainfrom
claude/repo-audit-improvement-ozhlau

Conversation

@paccloud

Copy link
Copy Markdown
Owner

Summary

Fixes four privacy and consent bugs found by re-triaging the closed audit issues against current main, plus the audit report and the Matt Pocock agent skills.

Fixes

Also included

  • AUDIT_REPORT.md, the June repository audit. It predates a lot of work on main, so treat it as a historical record.
  • 15 agent skills in .claude/skills/ (40 files) that the repo's CLAUDE.md and docs/agents/ config already point at. Say so if you would rather install these locally and I will drop that commit.

Review process

Each fix was implemented by one agent and then checked by two independent reviewers, one hunting for remaining leaks and one checking acceptance criteria and mutation-testing the new tests. Review findings were fixed before commit, including the consent race in #116 and a malformed-hash timing gap in #117.

Test plan

  • npm test in app/: 2,263 passing (baseline on main was 2,197)
  • npm run lint: 0 errors (2 pre-existing warnings)
  • npm run build: passes
  • Open the contributor profile page in a browser with an opted-in Postgres account and confirm the box shows checked and saving keeps it. The loading gate has no automated test because the repo has no React testing library.
  • Share a yield row and confirm the preview appears, cancel sends nothing, and confirm shares it.
  • Confirm the public /api/community-data and /api/contributors responses contain no username, email or user_id on the deployed preview.

Not fixed here

Needs an owner decision, all open on the tracker:

Closes #26, closes #115, closes #116, closes #117

🤖 Generated with Claude Code

https://claude.ai/code/session_01Dn84c88DDz5xSC19Z9c5Mg


Generated by Claude Code

claude and others added 10 commits June 10, 2026 11:48
The unauthenticated community-data feed attributed each shared yield row
to COALESCE(display_name, username). Firebase users' username is their
verified email, so sharing a row without a contributor profile published
the sharer's email, and the show_on_page consent flag was ignored.

- Move query + row shaping into the shared handler core
  (handleListCommunityData) with a new DbAdapter method
  listSharedYieldRows, implemented for Neon and SQLite. Neither query
  joins users, so no account identifier is ever selected.
- Attribution only with explicit consent: display name and organization
  appear only when a contributor profile exists and show_on_page is
  true (Postgres boolean or SQLite 1); otherwise anonymous.
- Output rows are allowlisted (id, species, product, yield, source,
  contributor, organization); yield normalized to a number on both
  backends. CSV keeps formula-injection sanitization.
- Express /api/community-data now honors ?format=csv like production;
  /api/export-community-data routes through the same handler.
- Sharing a yield row now opens ShareYieldModal listing exactly what
  becomes public and how it will be attributed; cancel sends no request,
  unshare stays one click.
- Tests: attribution cases, identifier-free JSON/CSV, SQL contents,
  in-memory SQLite parity, and the share/cancel/confirm flow.

Closes #26

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dn84c88DDz5xSC19Z9c5Mg
The branch was rebuilt on current main; its old tip carried an agent-skills
config commit that main now supersedes with its own. Recorded here as a
parent so the push is a fast-forward with no history rewritten. Tree is
unchanged from the tested fix commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dn84c88DDz5xSC19Z9c5Mg
The public contributors endpoint selected c.* plus u.username, so every
row carried the account username (a verified email for Firebase users),
the internal user_id and timestamps, even though the page renders only
five fields.

- Neon and SQLite listContributors now select only id, display_name,
  organization, bio and a contribution count. No join to users and no
  c.*, so a later edit cannot leak identifiers by accident.
- New allowlist shaper (shared/handlers/contributorsList.js) applied in
  handleListContributors; contribution_count is normalized to a number
  so both backends emit the same type.
- Profiles without a usable display name are not listed (the page would
  render an empty heading). Ordering gets an id tie-break for stability.
- Tests: planted-identifier fake adapter, exact key set, Neon SQL text
  (no users table, no c.*), and in-memory SQLite exclusion/count/blank
  name cases.

Closes #115

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dn84c88DDz5xSC19Z9c5Mg
Accounts created through Firebase sign-in have a NULL password. handleLogin
passed that to bcrypt.compare, which threw and returned 500, while a
nonexistent username returned 401. Anyone could therefore test whether an
email address had an account, and a Firebase user who mistyped their
password hit a server error via the login form's legacy fallback.

- A missing user, an account with no usable password, and a wrong
  password all return the same 401 "Invalid credentials".
- A bcrypt compare always runs, against a fixed valid dummy hash (cost 10,
  matching register) when there is no real one, so timing does not reveal
  which case occurred. A stored value that is not a well-formed bcrypt
  hash counts as no usable hash, since compare() would otherwise return
  instantly. Measured medians for all four cases are within ~2ms.
- Database errors still return 500; success, 400 validation and the
  misconfiguration guard are unchanged.
- Tests: null/empty/non-string/malformed stored passwords, identical
  responses, dummy compare on the not-found and no-hash paths.

Registration's 409 on a taken username is the same signal on the sign-up
side and is left to the decision in #27.

Closes #117

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dn84c88DDz5xSC19Z9c5Mg
The contributor profile form loaded its consent checkbox with
show_on_page === 1, which is only true for SQLite. Postgres returns a
real boolean, so an opted-in production user saw the box unchecked, and
saving any edit (e.g. a bio typo) withdrew their consent and removed
them from the contributors listing and community attribution.

- New app/src/lib/contributorProfile.js: normalizeShowOnPage (true, 1,
  'true' -> checked; anything else unchecked), strict isExplicitOptIn,
  profileToFormData, buildProfilePayload. The component uses them for
  initial state, load and the POST body; no inline comparison remains.
- The share preview's attribution check reuses the strict
  isExplicitOptIn so the preview never promises attribution the server
  withholds (the server does not treat the string 'true' as consent).
- Review found the mirror-image risk: if the profile GET failed or had
  not finished, the form kept its opted-in default and a user with
  stored consent off could save and silently grant it. The form and
  submit are now disabled until the load resolves with 200 or 404, and
  handleSubmit refuses to run before then.
- Tests: mapping table for boolean/int/string/garbage values and
  load-then-save round trips (true, 1, 'true', default).

Closes #116

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dn84c88DDz5xSC19Z9c5Mg
@vercel

vercel Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
fish-cost-calculator Ready Ready Preview Sep 29, 2026 2:32am UTC

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-29T02:41:57.927362Z cccca89 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 66e66b09-6f42-44c3-bea9-a6ae298da0dc

📥 Commits

Reviewing files that changed from the base of the PR and between fb365eb and 42ae823.

📒 Files selected for processing (63)
  • .claude/skills/caveman/SKILL.md
  • .claude/skills/diagnose/SKILL.md
  • .claude/skills/diagnose/scripts/hitl-loop.template.sh
  • .claude/skills/grill-me/SKILL.md
  • .claude/skills/grill-with-docs/ADR-FORMAT.md
  • .claude/skills/grill-with-docs/CONTEXT-FORMAT.md
  • .claude/skills/grill-with-docs/SKILL.md
  • .claude/skills/handoff/SKILL.md
  • .claude/skills/improve-codebase-architecture/DEEPENING.md
  • .claude/skills/improve-codebase-architecture/HTML-REPORT.md
  • .claude/skills/improve-codebase-architecture/INTERFACE-DESIGN.md
  • .claude/skills/improve-codebase-architecture/LANGUAGE.md
  • .claude/skills/improve-codebase-architecture/SKILL.md
  • .claude/skills/prototype/LOGIC.md
  • .claude/skills/prototype/SKILL.md
  • .claude/skills/prototype/UI.md
  • .claude/skills/setup-matt-pocock-skills/SKILL.md
  • .claude/skills/setup-matt-pocock-skills/domain.md
  • .claude/skills/setup-matt-pocock-skills/issue-tracker-github.md
  • .claude/skills/setup-matt-pocock-skills/issue-tracker-gitlab.md
  • .claude/skills/setup-matt-pocock-skills/issue-tracker-local.md
  • .claude/skills/setup-matt-pocock-skills/triage-labels.md
  • .claude/skills/tdd/SKILL.md
  • .claude/skills/tdd/deep-modules.md
  • .claude/skills/tdd/interface-design.md
  • .claude/skills/tdd/mocking.md
  • .claude/skills/tdd/refactoring.md
  • .claude/skills/tdd/tests.md
  • .claude/skills/teach/GLOSSARY-FORMAT.md
  • .claude/skills/teach/LEARNING-RECORD-FORMAT.md
  • .claude/skills/teach/MISSION-FORMAT.md
  • .claude/skills/teach/RESOURCES-FORMAT.md
  • .claude/skills/teach/SKILL.md
  • .claude/skills/to-issues/SKILL.md
  • .claude/skills/to-prd/SKILL.md
  • .claude/skills/triage/AGENT-BRIEF.md
  • .claude/skills/triage/OUT-OF-SCOPE.md
  • .claude/skills/triage/SKILL.md
  • .claude/skills/write-a-skill/SKILL.md
  • .claude/skills/zoom-out/SKILL.md
  • AUDIT_REPORT.md
  • api/_lib/neonDb.js
  • api/community-data.js
  • app/src/components/ContributorProfile.jsx
  • app/src/components/DataManagement.jsx
  • app/src/components/ShareYieldModal.jsx
  • app/src/lib/__tests__/communityDataHandler.test.js
  • app/src/lib/__tests__/contributorsListHandler.test.js
  • app/src/lib/__tests__/loginHandler.test.js
  • app/src/lib/apiClient.js
  • app/src/lib/contributorProfile.js
  • app/src/lib/contributorProfile.test.js
  • app/src/lib/yieldSharing.js
  • app/src/lib/yieldSharing.test.js
  • docs/API.md
  • server/adapters/sqliteDb.js
  • server/server.js
  • shared/db/interface.js
  • shared/handlers/communityData.js
  • shared/handlers/contributorsList.js
  • shared/handlers/index.js
  • shared/handlers/login.js
  • shared/handlers/publicEndpoints.js
 _________________________________________________________
< This PR is 10% code and 90% emotional support comments. >
 ---------------------------------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b421796981

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread app/src/components/ShareYieldModal.jsx
Comment thread shared/handlers/contributorsList.js
…rs docs

Addresses review feedback on #119.

- Sharing a yield row is now refused unless the preview resolved how the
  row will be credited. If the contributor-profile request failed, the
  server would still apply the stored show_on_page consent and could
  publish a name the confirmation never displayed. The flow controller
  (the only path that calls the share API) returns an error and makes no
  request while attribution is loading or unavailable, and the dialog
  disables Share and says why. A user with no profile (404) still shares,
  anonymously.
- docs/API.md: the GET /api/contributors example now shows the allowlisted
  response (no user_id, username or show_on_page).
- Tests for both blocked states and the no-profile path.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dn84c88DDz5xSC19Z9c5Mg

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8a0bfa003f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread app/src/components/ShareYieldModal.jsx Outdated
Comment thread app/src/lib/yieldSharing.js Outdated
Comment thread app/src/components/DataManagement.jsx
…rofile)

Addresses the second round of review feedback on #119.

- Focus trap: disabled buttons/inputs no longer count as the first or last
  focusable element, so Tab wraps inside the dialog while Share is
  disabled (loading, unavailable, or sending) instead of escaping it.
- A failed share (non-2xx or network error) is kept on the pending state
  and shown inside the open dialog with role="alert". It previously went
  to a page-level banner rendered behind the modal, leaving the user in
  the dialog with no explanation. The page banner is used only when the
  dialog has closed.
- A late profile response is now ignored unless it belongs to the exact
  preview that is still open. Comparing only the row let an old request
  mark a reopened preview "ready" with stale attribution. Verified by a
  test that fails against the old guard.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dn84c88DDz5xSC19Z9c5Mg

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 42ae823396

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread app/src/lib/yieldSharing.js
…review

The share API takes only the entry id and publishes the server copy, but
the dialog previews the local fields. For a row with an unsynced local
edit (or a conflict / pending delete) those differ: the dialog would
show one set of fields while the older server values went public, and
sharing bumps the server revision, so the pending edit would then
conflict and the stale values could stay public.

- getShareBlocker(): a row can be previewed and shared only when it
  exists on the server and its local syncStatus is 'synced'.
- request() refuses before opening any preview (no profile request, no
  dialog), and confirm() re-checks the previewed snapshot, so a row that
  stops being synced is never published. The user is told to sync first.
- Unsharing is unaffected: taking something private again is always
  allowed, including for rows with unsynced edits.
- Tests: every non-synced state is refused with no preview and no API
  call, the confirm-time re-check, and unshare still works.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dn84c88DDz5xSC19Z9c5Mg
@paccloud

Copy link
Copy Markdown
Owner Author

@codex yeah. Please review.

@paccloud
paccloud merged commit 7fb428a into main Sep 29, 2026
5 checks passed
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: cccca898e8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

paccloud pushed a commit that referenced this pull request Sep 29, 2026
main (#119) already stops public endpoints exposing emails and user ids,
with consent-aware attribution. Keep main's version of those files and
narrow this PR to the owner-id-from-token fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PnPh61Aps5neY87hh1pu9T

This branch was successfully deployed

1 active deployment
Preview — cccca898 Deployed Sep 29, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment