PII leak: 3 personal projects + 1 board flagged is_public=True #273

Closed
opened 2026-04-21 01:50:05 +00:00 by forgejo_admin · 0 comments
Contributor

Type

Bug

Lineage

Discovered while investigating pipeline #127 failures on pal-e-app PR #111. Four e2e/public-readiness.spec.ts tests flagged that anonymous visitors could see projects/boards that were supposed to be private.

Repo

forgejo_admin/pal-e-api

What Broke

The DB had three personal-use projects flagged is_public=True and one board with is_public=None:

  • project/private is_public=True → contains private thoughts (feedback_private_thought_pattern.md)
  • project/remember is_public=True → memory project
  • project/journal is_public=True → daily journal (project_journal.md)
  • board/board-private is_public=None

The API filter logic was correct — it correctly returns/withholds based on the flag — but the flag values were wrong, exposing private content via https://pal-e-docs.tail5b443a.ts.net/projects to anonymous callers.

Repro Steps

  1. Anonymous: curl -s https://pal-e-docs.tail5b443a.ts.net/projects | jq '.[] | select(.slug == "journal")'
  2. Pre-fix: returns the project with is_public:true
  3. Post-fix: filtered out

Expected Behavior

All three projects + the board are is_public=False. Anonymous /projects does NOT include them.

Environment

  • Cluster: prod, namespace pal-e-docs
  • API: https://pal-e-docs.tail5b443a.ts.net
  • Source of truth flag: projects.is_public and boards.is_public columns

Acceptance Criteria

  • project/private, project/remember, project/journal all is_public=False
  • board/board-private is_public=False
  • Anonymous /projects count drops by 3 (verified: 34 → 31)
  • Anonymous /boards count drops by 3 (verified: 36 → 33)
  • No regression to public projects (32 still public)

Action Taken (this session)

Wrote each via admin API with X-API-Key header:

  • PUT /projects/private -d '{"is_public": false}' → 200
  • PUT /projects/remember -d '{"is_public": false}' → 200
  • PUT /projects/journal -d '{"is_public": false}' → 200
  • PATCH /boards/board-private -d '{"is_public": false}' → 200

Per feedback_never_write_prod_db.md, this is the sanctioned admin-API path (not raw SQL).

Out of Scope (follow-ups)

  • Audit other projects/boards for similar miscategorization
  • Add a default is_public=False to schema so new projects fail closed (currently appears to default to True)
  • Add an API-level integration test that hits /projects anonymous and asserts no is_public=False rows leak
  • forgejo_admin/pal-e-app#111 — PR whose CI surfaced this
  • feedback_funnel_requires_auth.md — public-funnel services with private content require strict filtering
  • feedback_private_thought_pattern.md, project_journal.md — define these projects as private
### Type Bug ### Lineage Discovered while investigating pipeline #127 failures on pal-e-app PR #111. Four `e2e/public-readiness.spec.ts` tests flagged that anonymous visitors could see projects/boards that were supposed to be private. ### Repo `forgejo_admin/pal-e-api` ### What Broke The DB had three personal-use projects flagged `is_public=True` and one board with `is_public=None`: - `project/private` `is_public=True` → contains private thoughts (`feedback_private_thought_pattern.md`) - `project/remember` `is_public=True` → memory project - `project/journal` `is_public=True` → daily journal (`project_journal.md`) - `board/board-private` `is_public=None` The API filter logic was correct — it correctly returns/withholds based on the flag — but the flag values were wrong, exposing private content via `https://pal-e-docs.tail5b443a.ts.net/projects` to anonymous callers. ### Repro Steps 1. Anonymous: `curl -s https://pal-e-docs.tail5b443a.ts.net/projects | jq '.[] | select(.slug == "journal")'` 2. Pre-fix: returns the project with `is_public:true` 3. Post-fix: filtered out ### Expected Behavior All three projects + the board are `is_public=False`. Anonymous `/projects` does NOT include them. ### Environment - Cluster: prod, namespace `pal-e-docs` - API: `https://pal-e-docs.tail5b443a.ts.net` - Source of truth flag: `projects.is_public` and `boards.is_public` columns ### Acceptance Criteria - [x] `project/private`, `project/remember`, `project/journal` all `is_public=False` - [x] `board/board-private` `is_public=False` - [x] Anonymous `/projects` count drops by 3 (verified: 34 → 31) - [x] Anonymous `/boards` count drops by 3 (verified: 36 → 33) - [x] No regression to public projects (32 still public) ### Action Taken (this session) Wrote each via admin API with `X-API-Key` header: - `PUT /projects/private -d '{"is_public": false}'` → 200 - `PUT /projects/remember -d '{"is_public": false}'` → 200 - `PUT /projects/journal -d '{"is_public": false}'` → 200 - `PATCH /boards/board-private -d '{"is_public": false}'` → 200 Per `feedback_never_write_prod_db.md`, this is the sanctioned admin-API path (not raw SQL). ### Out of Scope (follow-ups) - Audit other projects/boards for similar miscategorization - Add a default `is_public=False` to schema so new projects fail closed (currently appears to default to True) - Add an API-level integration test that hits `/projects` anonymous and asserts no `is_public=False` rows leak ### Related - `forgejo_admin/pal-e-app#111` — PR whose CI surfaced this - `feedback_funnel_requires_auth.md` — public-funnel services with private content require strict filtering - `feedback_private_thought_pattern.md`, `project_journal.md` — define these projects as private
Commenting is not possible because the repository is archived.
No milestone
No project
No assignees
1 participant
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ldraney/pal-e-api#273
No description provided.