Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
-- =============================================================================
-- SahiDawa — Restrict Public Read on counterfeit_reports (PII breach #4200)
-- =============================================================================
-- PROBLEM:
-- The `reports_public_read` policy granted anon/authenticated `SELECT ...
-- USING (true)` over EVERY column of counterfeit_reports. Because the anon
-- key is embedded in client-side code, anyone could query the PostgREST REST
-- API and exfiltrate citizen reporters' personal data — reporter_phone, full
-- street address, pincode, and exact GPS coordinates (report_location) —
-- including reports still in a pending/unverified state.
--
-- FIX:
-- Drop the permissive policy, then expose ONLY a non-PII column allowlist to
-- anon/authenticated. Because table-level SELECT in PostgreSQL overrides
-- column grants, we revoke the table-level SELECT first, then re-grant the
-- individual safe columns. The RLS row policy (`USING (true)`) keeps the
-- public transparency model working for the allowed columns.
--
-- Blocked (PII / internal-only) columns:
-- reporter_id, reporter_phone, address, pincode, report_location
-- ip_address, report_hash, risk_score, is_escalated,
-- duplicate_group_id, snoozed_until
--
-- IMPACT:
-- Privileged server-side flows use the RLS-bypassing service_role key, so
-- the API report flows (submit, /mine, admin list) are unaffected. Only the
-- anon/authenticated surface—which previously exposed ALL columns—is now
-- scoped to the non-PII allowlist.
-- =============================================================================

-- 1. Undefine the "anyone can read every column" policy.
DROP POLICY IF EXISTS "reports_public_read" ON public.counterfeit_reports;

-- 2. Remove table-level SELECT for client roles so the column-level grants in
-- step 3 become the effective access surface. INSERT (report submission)
-- and owner UPDATE are intentionally untouched.
REVOKE SELECT ON public.counterfeit_reports FROM anon, authenticated;

-- 3. Re-add a row-level policy so public transparency reads still return rows.
CREATE POLICY "reports_public_read_non_pii"
ON public.counterfeit_reports
FOR SELECT
TO anon, authenticated
USING (true);

-- 4. Grant SELECT on exactly the non-PII display columns.
GRANT SELECT (
id,
medicine_id,
scanned_barcode,
reported_brand_name,
manufacturer,
description,
pharmacy_name,
city,
state,
photo_url,
photo_urls,
district,
status,
created_at
) ON public.counterfeit_reports TO anon, authenticated;
123 changes: 123 additions & 0 deletions supabase/tests/rls/counterfeit_reports_public_read.test.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
BEGIN;

CREATE EXTENSION IF NOT EXISTS pgtap;

SELECT plan(6);

-- RLS must be enabled on counterfeit_reports
SELECT ok(
(
SELECT relrowsecurity
FROM pg_class
WHERE oid = 'public.counterfeit_reports'::regclass
),
'RLS enabled on counterfeit_reports'
);

-- --------------------------------------------------------------------
-- Seed a report row (as an admin/service-role context) that carries PII.
-- --------------------------------------------------------------------
INSERT INTO public.counterfeit_reports
(
id,
medicine_id,
scanned_barcode,
reported_brand_name,
manufacturer,
description,
pharmacy_name,
city,
state,
address,
pincode,
photo_url,
photo_urls,
report_location,
district,
status,
reporter_phone,
created_at
)
VALUES
(
'dddddddd-0000-4000-8000-000000000004',

Check failure on line 43 in supabase/tests/rls/counterfeit_reports_public_read.test.sql

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal 6 times.

See more on https://sonarcloud.io/project/issues?id=RatLoopz_sahidawa-india&issues=AZ_cW_FCRt4gBeCJjsgR&open=AZ_cW_FCRt4gBeCJjsgR&pullRequest=4209
NULL,
'BC-100',
'Acme-100',
'Acme Pharma',
'suspicious batch',
'City Pharmacy',
'Jaipur',
'Rajasthan',
'1 Main Street, Jaipur, RJ 302001',
'302001',
'https://cloud.example/photo.jpg',
ARRAY['https://cloud.example/photo.jpg']::text[],
ST_GeomFromText('POINT(75.7873 26.9124)', 4326)::geography,
'verified_fake',
'+911234567890'
) ON CONFLICT (id) DO NOTHING;

-- --------------------------------------------------------------------
-- Act as the anonymous client.
-- --------------------------------------------------------------------
SET LOCAL ROLE anon;

-- Anonymous client may read only the non-PII display columns.
SELECT lives_ok(
$$
SELECT reported_brand_name, district, status, created_at
FROM public.counterfeit_reports
WHERE id = 'dddddddd-0000-4000-8000-000000000004';
$$,
'anon can read non-PII display columns'
);

-- An anonymous client must NOT be able to read reporter PII columns.
SELECT throws_ok(
$$
SELECT reporter_phone
FROM public.counterfeit_reports
WHERE id = 'dddddddd-0000-4000-8000-000000000004';
$$,
'42501',

Check failure on line 83 in supabase/tests/rls/counterfeit_reports_public_read.test.sql

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal 4 times.

See more on https://sonarcloud.io/project/issues?id=RatLoopz_sahidawa-india&issues=AZ_cW_FDRt4gBeCJjsgS&open=AZ_cW_FDRt4gBeCJjsgS&pullRequest=4209
NULL,
'anon cannot read reporter_phone'
);

SELECT throws_ok(
$$
SELECT address
FROM public.counterfeit_reports
WHERE id = 'dddddddd-0000-4000-8000-000000000004';
$$,
'42501',
NULL,
'anon cannot read reporter address'
);

SELECT throws_ok(
$$
SELECT pincode
FROM public.counterfeit_reports
WHERE id = 'dddddddd-0000-4000-8000-000000000004';
$$,
'42501',
NULL,
'anon cannot read reporter pincode'
);

SELECT throws_ok(
$$
SELECT report_location
FROM public.counterfeit_reports
WHERE id = 'dddddddd-0000-4000-8000-000000000004';
$$,
'42501',
NULL,
'anon cannot read report GPS coordinates'
);

SELECT * FROM finish();

ROLLBACK;
Loading