# Pattern 01 — Anon-role omission

## The failure

AI generates a SELECT policy without specifying `TO authenticated`:

```sql
CREATE POLICY "read_own_profile" ON profiles
  FOR SELECT USING (auth.uid() = id);
```

This compiles cleanly and behaves correctly for authenticated users. The problem is what happens for `anon` users: `auth.uid()` returns `NULL`, so `NULL = id` evaluates to `NULL` (not `false`). In a USING clause, `NULL` means the row is invisible — which is the correct end result. But:

1. The developer expects anon users to receive a 401 or 403. They get an empty `[]` instead.
2. Client-side code that branches on "got data vs got auth error" silently falls through the wrong branch.
3. If your table has a different column type or you ever add an index-only policy for anon, the original policy's interaction with `anon` becomes unpredictable.

The subtler version of this bug: if you have two policies — one permitting authenticated access and one inadvertently permitting anon access — the anon policy broadens access (see pattern 03).

## The fix

Always specify the role target when the policy is for authenticated users only:

```sql
CREATE POLICY "authenticated_read_own_profile"
  ON profiles
  FOR SELECT
  TO authenticated
  USING (auth.uid() = id);
```

For clarity, add an explicit `anon` deny policy. It's redundant (RLS blocks by default) but makes intent obvious in a schema audit:

```sql
CREATE POLICY "block_anon_read"
  ON profiles
  FOR SELECT
  TO anon
  USING (false);
```

## When to check for this

- Any table with user-specific data where you don't want anonymous reads
- Any policy that uses `auth.uid()` without a `TO` clause
- Supabase policy editor output: the generated SQL omits `TO` by default — add it manually

## Testing it

```sql
-- In Supabase SQL editor, switch to anon key in your client
-- or test with set_config:
SET role anon;
SET request.jwt.claims TO '{}';
SELECT * FROM profiles; -- Should return 0 rows, not error (confirm this is expected)
RESET role;
```
