# Scenario: Permission Phase 0 Security Verification

skip: true

**Source Branch:** `fix/permission-phase0-security`
**Date:** 2026-04-05
**Method:** 10-agent parallel design review → scenario test creation → reproduce → fix → regress

---

## Context

Design review by 10 specialized agents found 6 CRITICAL, 13 HIGH, 8 MEDIUM issues
in the permission Phase 0 implementation. This document tracks the scenario tests
that verify each finding against the real system.

The core gap: unit tests verified internal logic (mock layer) but no scenario tests
exercised security boundaries against real DB/HTTP with multiple user roles.

---

## Pre-conditions

| ID | Check | How to verify |
|----|-------|---------------|
| PRE-01 | Admin user exists with system_admin profile | `POST /services/oauth2/token` with admin credentials |
| PRE-02 | Sales user (sales@test.com) exists with standard profile | `POST /services/oauth2/token` with sales credentials |
| PRE-03 | Account object has SharingModel != PublicReadWrite | `SELECT SharingModel FROM _System_Object WHERE ApiName = 'Account'` |
| PRE-04 | Contact object has SharingModel = ControlledByParent | `SELECT SharingModel FROM _System_Object WHERE ApiName = 'Contact'` |

---

## Test Cases

### RLS Column Name Correctness

| ID | Step | Expected | Evidence |
|----|------|----------|----------|
| SEC-001 | Create RecordShare for Sales user on admin's Account. Sales user queries Accounts via SOQL. | 200 OK with shared record visible | `rls_filter.go:294` uses `rs.SobjectType` but table has `ObjectApiName` |
| SEC-002 | Create TeamMember for Sales user on admin's Account. Sales user queries Accounts. | 200 OK with team-visible record | `rls_filter.go:304` uses `tm.SobjectType` but table has `ObjectApiName` |

### Reproduction (SEC-001)

```bash
# Get admin token
ADMIN_TOKEN=$(curl -s -X POST http://localhost:8082/services/oauth2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=password" \
  --data-urlencode "username=admin@test.com" \
  --data-urlencode "password=${NEXUS_ADMIN_PASSWORD}" | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")

# Get sales token
SALES_TOKEN=$(curl -s -X POST http://localhost:8082/services/oauth2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=password" \
  --data-urlencode "username=sales@test.com" \
  --data-urlencode "password=Sales123!" | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")

# Admin creates Account
ACCT_ID=$(curl -s -X POST http://localhost:8082/services/data/v63.0/sobjects/Account \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"Name":"SEC001-Test"}' | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")

# Get Sales user ID
SALES_ID=$(curl -s http://localhost:8082/services/data/v63.0/query?q=SELECT+Id+FROM+User+WHERE+Email=%27sales@test.com%27 \
  -H "Authorization: Bearer $ADMIN_TOKEN" | python3 -c "import sys,json; print(json.load(sys.stdin)['records'][0]['Id'])")

# Create manual share
curl -s -X POST http://localhost:8082/services/data/v63.0/sobjects/_System_RecordShare \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"ObjectApiName\":\"Account\",\"RecordId\":\"$ACCT_ID\",\"ShareWithUserId\":\"$SALES_ID\",\"AccessLevel\":\"Read\",\"Reason\":\"Manual\"}"

# Sales user queries — this triggers the RLS subquery with column names
curl -s "http://localhost:8082/services/data/v63.0/query?q=SELECT+Id,Name+FROM+Account+WHERE+Id=%27$ACCT_ID%27" \
  -H "Authorization: Bearer $SALES_TOKEN"

# Expected: 200 with record visible
# Actual (bug): 500 with "Unknown column 'SobjectType' in 'where clause'"
```

### ControlledByParent Consistency

| ID | Step | Expected | Evidence |
|----|------|----------|----------|
| SEC-003 | Admin creates Account (shared with Sales), creates Contact under it. Sales user GETs Contact by ID. | 200 OK (Contact inherits Account access) | `record_access.go:28-38` switch has no ControlledByParent case |
| SEC-009 | Same Contact: compare SOQL query result vs GET by ID. Both must agree. | Both found=true OR both found=false | Design invariant: `R visible in SOQL ⟺ CheckRecordAccess(U, R, Read) == true` |

### Session Lifecycle

| ID | Step | Expected | Evidence |
|----|------|----------|----------|
| SEC-004 | Login as temp user, get token. Admin deletes user. Use old token. | Token rejected (401/403) | `auth_user_mgmt.go:195-211` — no `RevokeAllSessionsForUser` call |

### Reproduction (SEC-004)

```bash
# Create temp user (as admin)
curl -s -X POST http://localhost:8082/services/data/v63.0/sobjects/User \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"Username":"sec004@test.com","Email":"sec004@test.com","FirstName":"SEC004","LastName":"Test","Password":"TempPass123!","ProfileId":"standard_user","IsActive":true}'

# Login as temp user
TEMP_TOKEN=$(curl -s -X POST http://localhost:8082/services/oauth2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=password" \
  --data-urlencode "username=sec004@test.com" \
  --data-urlencode "password=TempPass123!" | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")

# Verify token works
curl -s http://localhost:8082/services/data/ -H "Authorization: Bearer $TEMP_TOKEN"
# Expected: 200 OK

# Admin deletes user
TEMP_ID=<from create response>
curl -s -X DELETE "http://localhost:8082/services/data/v63.0/sobjects/User/$TEMP_ID" \
  -H "Authorization: Bearer $ADMIN_TOKEN"

# Use old token
curl -s http://localhost:8082/services/data/ -H "Authorization: Bearer $TEMP_TOKEN"
# Expected: 401 Unauthorized
# Actual (bug): 200 OK (session not revoked)
```

### Route Security

| ID | Step | Expected | Evidence |
|----|------|----------|----------|
| SEC-005 | GET /debug/pprof/heap without auth token | 401/403 or 404 (pprof disabled) | `main.go:333` — zero middleware on pprof group |
| SEC-006 | GET file download endpoint without auth | 401/403 | `file_upload_handler.go:136-188` — zero permission check |
| SEC-007 | Admin POSTs to _System_Session | Rejected (BLOCKED) | `system_tables.json` — RequiredWritePermission = BLOCKED |
| SEC-008 | 20 rapid invalid token requests | 429 before 20th request | `routes.go:47` — shares global 100 req/s limiter |
| SEC-010 | Create roles A→B, set A.parent=B (cycle) | Validation error | `service.go:580-605` — only self-reference check |

---

## Execution Checklist

```
PRE-01: [ ] PRE-02: [ ] PRE-03: [ ] PRE-04: [ ]
SEC-001: [ ] SEC-002: [ ] SEC-003: [ ] SEC-004: [ ] SEC-005: [ ]
SEC-006: [ ] SEC-007: [ ] SEC-008: [ ] SEC-009: [ ] SEC-010: [ ]
```

---

## Mutual Improvement Cycle

```
Design doc defines security rules
       ↓
Agent review finds implementation gaps (this round: 6C/13H/8M)
       ↓
Scenario tests reproduce each gap on real system
       ↓
Fix code to pass scenarios
       ↓
Scenarios become permanent regression guards
       ↓
Next design update → update scenarios first → then code (TDD)
       ↓
Agent review checks design/test/code alignment
```

Each round tightens the alignment between design, tests, and code.
The key insight: unit tests verify internal logic; scenario tests verify
security boundaries. Both are needed — neither is sufficient alone.
