From abd3d48c6e418080032cd5a921a286d41ba7aab3 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 2 Sep 2026 10:42:06 +0000 Subject: [PATCH 1/2] issues: queue the #Z61JRT public-bucket RLS correction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The archived #Z61JRT close outcome asserts that flipping the clinical-images bucket to public would still not produce a working read path, because the "image storage owner read" RLS policy keys on the first path segment equalling auth.uid(). That is wrong, and wrong in the unsafe direction. The installed client documents getPublicUrl as requiring "buckets table permissions: none" and "objects table permissions: none" (node_modules/@supabase/storage-js/src/packages/StorageFileApi.ts). A public bucket serves its objects with no RLS evaluated and no token, so flipping clinical-images would make every object anonymously readable by URL — the exact hazard the original row existed to warn about. Archived rows are immutable history: updateIssue refuses them and check:ledger-write-discipline rejects direct table-row edits. The correction is therefore filed as a new open P2 row that quotes the erroneous sentence, gives the evidence, and states that the archived outcome must be read with it. No code fix is owed. The bucket was not flipped — a read-only query on 2026-09-02 returned public = false for both buckets, and 20260717139000_create_storage_buckets.sql re-asserts that on every replay — and the shipped route signs uniformly via createSignedUrls, with zero getPublicUrl matches repo-wide. Raised by the Codex reviewer on PR #2559 and verified against the installed source rather than accepted on assertion. This PR only queues the request. Reconciliation happens in a later run, as the write-discipline gate requires. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01AcjoJHKAPfBPKYgwTF3wTS --- .../ac026ec0-6070-4623-aecc-c9a1af2f312a.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 docs/outstanding-issues-inbox/ac026ec0-6070-4623-aecc-c9a1af2f312a.json diff --git a/docs/outstanding-issues-inbox/ac026ec0-6070-4623-aecc-c9a1af2f312a.json b/docs/outstanding-issues-inbox/ac026ec0-6070-4623-aecc-c9a1af2f312a.json new file mode 100644 index 000000000..f4304e144 --- /dev/null +++ b/docs/outstanding-issues-inbox/ac026ec0-6070-4623-aecc-c9a1af2f312a.json @@ -0,0 +1,14 @@ +{ + "version": 2, + "id": "ac026ec0-6070-4623-aecc-c9a1af2f312a", + "createdOn": "2026-09-02", + "action": "add", + "payload": { + "pri": "P2", + "type": "issue", + "summary": "Archived #Z61JRT carries an incorrect and dangerous public-bucket RLS claim", + "detail": "CORRECTION to the archived #Z61JRT close outcome. Archived rows are immutable history and are not edited, so the erroneous sentence stays in the archive and this row is the correction of record. Anyone reading that archived outcome must read this row with it.\n\nTHE ERROR: the #Z61JRT outcome states \"Flipping the bucket would also not have produced a working read path: the storage RLS policy 'image storage owner read' keys on the first path segment equalling auth.uid(), which a published ownerless document never satisfies, so signed URLs minted with the service-role key are the only mechanism that serves public-corpus figures.\" That is wrong, and wrong in the unsafe direction.\n\nWHY IT IS WRONG: the owner-folder RLS policy on storage.objects governs authenticated reads of a PRIVATE bucket. It has no bearing on a public one. The installed client documents this explicitly for getPublicUrl at node_modules/@supabase/storage-js/src/packages/StorageFileApi.ts:1080-1083, which lists the required permissions as \"buckets table permissions: none\" and \"objects table permissions: none\". A public bucket therefore serves its objects to anyone with the URL, with no RLS evaluated at all and no token required.\n\nTHE CORRECT STATEMENT: if clinical-images were ever flipped to public, every object in it would become anonymously readable by URL, permanently and without authentication. The owner-read policy would not stop it. That is precisely the hazard the original #Z61JRT row warned about, so the archived text inverts the risk it was recording and would invite a reader to believe flipping the bucket is inert.\n\nWHAT REMAINS TRUE AND UNCHANGED: the bucket was not flipped. A read-only live query on 2026-09-02 returned storage.buckets.public = false for both clinical-images and clinical-documents, and migration 20260717139000_create_storage_buckets.sql re-asserts public = false on every replay, so a manual dashboard flip is reverted by the next migration run. The delivered fix in the batch image route is also unaffected: it signs uniformly via createSignedUrls and never calls getPublicUrl, and grep for getPublicUrl across src, worker, scripts and supabase still returns zero matches. So there is no live exposure and no code change is owed by this correction. What was wrong was the stated reason a bucket flip would be harmless, not the conclusion that the bucket must stay private.\n\nPROVENANCE: raised by the Codex reviewer as a P2 finding on PR #2559 against docs/outstanding-issues.md:623, verified against the installed storage-js source rather than accepted on assertion. The original claim came from a schema review earlier in the same session and was carried into the close outcome without being checked against the public-bucket path.\n\nNEXT: no code fix. Treat this row as the authoritative reading of that archived outcome. If a future task ever proposes making clinical-images public, this row is the reason not to, and the bucket-visibility assertion in the migration is the control that enforces it.", + "source": "Codex review finding (P2) on PR #2559, docs/outstanding-issues.md:625, 2026-09-02; verified against node_modules/@supabase/storage-js StorageFileApi.ts getPublicUrl remarks", + "issueUlid": "01M1GV7C0GE6BB64ABG6WKTF7X" + } +} From fd6b5aecb004668ff139eb23729dfebbebb97356 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 2 Sep 2026 10:56:54 +0000 Subject: [PATCH 2/2] =?UTF-8?q?issues:=20correct=20the=20correction=20?= =?UTF-8?q?=E2=80=94=20the=20bucket=20migration=20does=20not=20auto-revert?= =?UTF-8?q?=20a=20live=20flip?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ac026ec0-6070-4623-aecc-c9a1af2f312a.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/outstanding-issues-inbox/ac026ec0-6070-4623-aecc-c9a1af2f312a.json b/docs/outstanding-issues-inbox/ac026ec0-6070-4623-aecc-c9a1af2f312a.json index f4304e144..083fe314c 100644 --- a/docs/outstanding-issues-inbox/ac026ec0-6070-4623-aecc-c9a1af2f312a.json +++ b/docs/outstanding-issues-inbox/ac026ec0-6070-4623-aecc-c9a1af2f312a.json @@ -7,7 +7,7 @@ "pri": "P2", "type": "issue", "summary": "Archived #Z61JRT carries an incorrect and dangerous public-bucket RLS claim", - "detail": "CORRECTION to the archived #Z61JRT close outcome. Archived rows are immutable history and are not edited, so the erroneous sentence stays in the archive and this row is the correction of record. Anyone reading that archived outcome must read this row with it.\n\nTHE ERROR: the #Z61JRT outcome states \"Flipping the bucket would also not have produced a working read path: the storage RLS policy 'image storage owner read' keys on the first path segment equalling auth.uid(), which a published ownerless document never satisfies, so signed URLs minted with the service-role key are the only mechanism that serves public-corpus figures.\" That is wrong, and wrong in the unsafe direction.\n\nWHY IT IS WRONG: the owner-folder RLS policy on storage.objects governs authenticated reads of a PRIVATE bucket. It has no bearing on a public one. The installed client documents this explicitly for getPublicUrl at node_modules/@supabase/storage-js/src/packages/StorageFileApi.ts:1080-1083, which lists the required permissions as \"buckets table permissions: none\" and \"objects table permissions: none\". A public bucket therefore serves its objects to anyone with the URL, with no RLS evaluated at all and no token required.\n\nTHE CORRECT STATEMENT: if clinical-images were ever flipped to public, every object in it would become anonymously readable by URL, permanently and without authentication. The owner-read policy would not stop it. That is precisely the hazard the original #Z61JRT row warned about, so the archived text inverts the risk it was recording and would invite a reader to believe flipping the bucket is inert.\n\nWHAT REMAINS TRUE AND UNCHANGED: the bucket was not flipped. A read-only live query on 2026-09-02 returned storage.buckets.public = false for both clinical-images and clinical-documents, and migration 20260717139000_create_storage_buckets.sql re-asserts public = false on every replay, so a manual dashboard flip is reverted by the next migration run. The delivered fix in the batch image route is also unaffected: it signs uniformly via createSignedUrls and never calls getPublicUrl, and grep for getPublicUrl across src, worker, scripts and supabase still returns zero matches. So there is no live exposure and no code change is owed by this correction. What was wrong was the stated reason a bucket flip would be harmless, not the conclusion that the bucket must stay private.\n\nPROVENANCE: raised by the Codex reviewer as a P2 finding on PR #2559 against docs/outstanding-issues.md:623, verified against the installed storage-js source rather than accepted on assertion. The original claim came from a schema review earlier in the same session and was carried into the close outcome without being checked against the public-bucket path.\n\nNEXT: no code fix. Treat this row as the authoritative reading of that archived outcome. If a future task ever proposes making clinical-images public, this row is the reason not to, and the bucket-visibility assertion in the migration is the control that enforces it.", + "detail": "CORRECTION to the archived #Z61JRT close outcome. Archived rows are immutable history and are not edited, so the erroneous sentence stays in the archive and this row is the correction of record. Anyone reading that archived outcome must read this row with it.\n\nTHE ERROR: the #Z61JRT outcome states \"Flipping the bucket would also not have produced a working read path: the storage RLS policy 'image storage owner read' keys on the first path segment equalling auth.uid(), which a published ownerless document never satisfies, so signed URLs minted with the service-role key are the only mechanism that serves public-corpus figures.\" That is wrong, and wrong in the unsafe direction.\n\nWHY IT IS WRONG: the owner-folder RLS policy on storage.objects governs authenticated reads of a PRIVATE bucket. It has no bearing on a public one. The installed client documents this explicitly for getPublicUrl at node_modules/@supabase/storage-js/src/packages/StorageFileApi.ts, which lists the required permissions as \"buckets table permissions: none\" and \"objects table permissions: none\". A public bucket therefore serves its objects to anyone with the URL, with no RLS evaluated at all and no token required.\n\nTHE CORRECT STATEMENT: if clinical-images were ever flipped to public, every object in it would become anonymously readable by URL, without authentication. The owner-read policy would not stop it. That is precisely the hazard the original #Z61JRT row warned about, so the archived text inverts the risk it was recording and would invite a reader to believe flipping the bucket is inert.\n\nCURRENT LIVE STATE: the bucket was not flipped. A user-authorised read-only query on 2026-09-02 returned storage.buckets.public = false for both clinical-images and clinical-documents. The delivered fix in the batch image route is also unaffected: it signs uniformly via createSignedUrls and never calls getPublicUrl, and grep for getPublicUrl across src, worker, scripts and supabase returns zero matches. So there is no live exposure and no code change is owed by this correction. What was wrong was the stated reason a bucket flip would be harmless, not the conclusion that the bucket must stay private.\n\nWHAT THE MIGRATION ACTUALLY GUARANTEES, AND WHAT IT DOES NOT: 20260717139000_create_storage_buckets.sql carries \"on conflict (id) do update set public = false\", so it pins the bucket private whenever that migration itself executes \u2014 that is, on a fresh replay such as a local db reset or the CI migration-replay job. It does NOT re-run against the live project. Migrations apply once and are then recorded in the remote history table; check-migration-history-alignment.ts classifies only local versions ABSENT from remote history as pending apply, so an already-recorded version is never pending again and its ON CONFLICT clause never fires again. An earlier draft of this row claimed a manual dashboard flip would be \"reverted by the next migration run\". That was false, and false in the same containment-overstating direction as the error this row exists to correct.\n\nSO THE REAL CONTROL IS DETECTION, NOT PREVENTION: if an operator flipped clinical-images to public in the dashboard, it would STAY public until a person explicitly restored it. schema_drift_snapshot captures storage_buckets, so npm run check:drift would surface the changed public value as drift, and the post-merge live-drift workflow is where that surfaces. But check:drift only reports; it repairs nothing. Remediation is manual \u2014 flip it back in the dashboard or apply a new migration that re-asserts public = false, since only a NEW version would be pending and therefore actually execute.\n\nPROVENANCE: the primary error was raised by the Codex reviewer as a P2 finding on PR #2559 against docs/outstanding-issues.md, verified against the installed storage-js source rather than accepted on assertion. The original claim came from a schema review earlier in the same session and was carried into the close outcome without being checked. The secondary error \u2014 the false automatic-reversion claim in this very correction \u2014 was then raised by the same reviewer on PR #2563 and verified against check-migration-history-alignment.ts before this text was rewritten. Both corrections were made before this request was reconciled, so the ledger never carried the second error.\n\nNEXT: no code fix. Treat this row as the authoritative reading of that archived outcome. If a future task ever proposes making clinical-images public, this row is the reason not to. Do not rely on the migration to undo such a change on the live project; rely on check:drift to notice it and on a person to put it back.", "source": "Codex review finding (P2) on PR #2559, docs/outstanding-issues.md:625, 2026-09-02; verified against node_modules/@supabase/storage-js StorageFileApi.ts getPublicUrl remarks", "issueUlid": "01M1GV7C0GE6BB64ABG6WKTF7X" }