# Pattern 05 — INSERT without WITH CHECK

## The failure

AI generates an INSERT policy using USING instead of WITH CHECK:

```sql
CREATE POLICY "insert_own_post" ON posts
  FOR INSERT USING (auth.uid() = user_id);
```

This compiles and runs without any error. The USING clause is simply ignored for INSERT operations. The effective result: any authenticated user can insert a post with any `user_id` they like — including another user's UUID.

## Why this is dangerous

In a typical Supabase app, `user_id` is set by the client before the INSERT. If WITH CHECK isn't enforced, a malicious user can:

1. Construct a request that sets `user_id` to another user's UUID
2. INSERT a row that appears to belong to that other user
3. Depending on your SELECT policy, that other user might now see the forged row as their own

This is most serious in shared apps (comments, messages, posts) where rows from other users are displayed in another user's feed.

## The fix

For INSERT, use WITH CHECK:

```sql
CREATE POLICY "insert_own_post"
  ON posts
  FOR INSERT
  TO authenticated
  WITH CHECK (auth.uid() = user_id);
```

## USING vs WITH CHECK — when each applies

| Operation | USING | WITH CHECK |
|-----------|-------|------------|
| SELECT | ✅ filters rows returned | not applicable |
| INSERT | silently ignored | ✅ validates the new row |
| UPDATE | ✅ filters rows you can update | ✅ validates the updated row |
| DELETE | ✅ filters rows you can delete | not applicable |

For UPDATE you need both: USING to determine which rows you can touch, and WITH CHECK to ensure you don't change them into something invalid (see pattern 06).

## Verifying the fix

Test that a user cannot forge another user's UUID:

```sql
-- As user A (in your test client):
INSERT INTO posts (user_id, title) VALUES ('<user_B_uuid>', 'Forged post');
-- Should fail with: new row violates row-level security policy for table "posts"
```

If it succeeds, your INSERT policy is missing WITH CHECK.
