openhack 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. openhack/__init__.py +2 -0
  2. openhack/__main__.py +225 -0
  3. openhack/agents/__init__.py +30 -0
  4. openhack/agents/base.py +230 -0
  5. openhack/agents/browser_verifier.py +679 -0
  6. openhack/agents/browser_verifier_swarm.py +256 -0
  7. openhack/agents/checkpoint.py +89 -0
  8. openhack/agents/context_manager.py +356 -0
  9. openhack/agents/coordinator.py +1105 -0
  10. openhack/agents/endpoint_analyst.py +307 -0
  11. openhack/agents/feature_hunter.py +93 -0
  12. openhack/agents/hunter.py +481 -0
  13. openhack/agents/hunter_swarm.py +385 -0
  14. openhack/agents/llm.py +334 -0
  15. openhack/agents/recon.py +19 -0
  16. openhack/agents/sandbox_verifier.py +396 -0
  17. openhack/agents/sandbox_verifier_swarm.py +250 -0
  18. openhack/agents/session.py +286 -0
  19. openhack/agents/validator.py +217 -0
  20. openhack/agents/validator_swarm.py +106 -0
  21. openhack/auth.py +175 -0
  22. openhack/browser/__init__.py +12 -0
  23. openhack/browser/runner.py +385 -0
  24. openhack/categories.py +130 -0
  25. openhack/config.py +201 -0
  26. openhack/deterministic_recon.py +464 -0
  27. openhack/entry_points.py +745 -0
  28. openhack/framework_classifier.py +515 -0
  29. openhack/framework_detection.py +269 -0
  30. openhack/headless_scan.py +179 -0
  31. openhack/prompts/__init__.py +108 -0
  32. openhack/prompts/browser_verifier.py +171 -0
  33. openhack/prompts/coordinator.py +31 -0
  34. openhack/prompts/django/__init__.py +32 -0
  35. openhack/prompts/django/auth_bypass.py +76 -0
  36. openhack/prompts/django/csrf.py +62 -0
  37. openhack/prompts/django/data_exposure.py +67 -0
  38. openhack/prompts/django/idor.py +74 -0
  39. openhack/prompts/django/injection.py +67 -0
  40. openhack/prompts/django/misconfiguration.py +70 -0
  41. openhack/prompts/django/ssrf.py +64 -0
  42. openhack/prompts/endpoint_analyst.py +122 -0
  43. openhack/prompts/express/__init__.py +29 -0
  44. openhack/prompts/express/auth_bypass.py +71 -0
  45. openhack/prompts/express/data_exposure.py +77 -0
  46. openhack/prompts/express/idor.py +69 -0
  47. openhack/prompts/express/injection.py +75 -0
  48. openhack/prompts/express/misconfiguration.py +72 -0
  49. openhack/prompts/express/ssrf.py +63 -0
  50. openhack/prompts/feature_hunter.py +140 -0
  51. openhack/prompts/flask/__init__.py +29 -0
  52. openhack/prompts/flask/auth_bypass.py +86 -0
  53. openhack/prompts/flask/data_exposure.py +78 -0
  54. openhack/prompts/flask/idor.py +83 -0
  55. openhack/prompts/flask/injection.py +77 -0
  56. openhack/prompts/flask/misconfiguration.py +73 -0
  57. openhack/prompts/flask/ssrf.py +65 -0
  58. openhack/prompts/hunter.py +362 -0
  59. openhack/prompts/hunter_continuation_loop.py +12 -0
  60. openhack/prompts/hunter_continuation_no_findings.py +19 -0
  61. openhack/prompts/hunter_continuation_no_progress.py +22 -0
  62. openhack/prompts/hunter_tool_instructions.py +55 -0
  63. openhack/prompts/nextjs/__init__.py +42 -0
  64. openhack/prompts/nextjs/auth_bypass.py +80 -0
  65. openhack/prompts/nextjs/csrf.py +71 -0
  66. openhack/prompts/nextjs/data_exposure.py +88 -0
  67. openhack/prompts/nextjs/idor.py +64 -0
  68. openhack/prompts/nextjs/injection.py +65 -0
  69. openhack/prompts/nextjs/middleware_bypass.py +75 -0
  70. openhack/prompts/nextjs/misconfiguration.py +92 -0
  71. openhack/prompts/nextjs/server_actions.py +97 -0
  72. openhack/prompts/nextjs/ssrf.py +66 -0
  73. openhack/prompts/nextjs/xss.py +69 -0
  74. openhack/prompts/pr_analysis_system.py +80 -0
  75. openhack/prompts/pr_analysis_user.py +11 -0
  76. openhack/prompts/project_context.py +89 -0
  77. openhack/prompts/recon.py +199 -0
  78. openhack/prompts/reporter.py +88 -0
  79. openhack/prompts/researchers.py +434 -0
  80. openhack/prompts/sandbox_verifier.py +128 -0
  81. openhack/prompts/supabase/__init__.py +39 -0
  82. openhack/prompts/supabase/auth_tokens.py +131 -0
  83. openhack/prompts/supabase/edge_functions.py +150 -0
  84. openhack/prompts/supabase/graphql.py +102 -0
  85. openhack/prompts/supabase/postgrest.py +99 -0
  86. openhack/prompts/supabase/realtime.py +93 -0
  87. openhack/prompts/supabase/rls.py +110 -0
  88. openhack/prompts/supabase/rpc_functions.py +127 -0
  89. openhack/prompts/supabase/storage.py +110 -0
  90. openhack/prompts/supabase/tenant_isolation.py +118 -0
  91. openhack/prompts/validator.py +319 -0
  92. openhack/prompts/validator_continuation_incomplete.py +12 -0
  93. openhack/prompts/validator_tool_instructions.py +29 -0
  94. openhack/quality.py +231 -0
  95. openhack/sandbox/__init__.py +12 -0
  96. openhack/sandbox/orchestrator.py +517 -0
  97. openhack/sandbox/runner.py +177 -0
  98. openhack/scan_session.py +245 -0
  99. openhack/setup.py +452 -0
  100. openhack/static_validator.py +612 -0
  101. openhack/tools/__init__.py +1 -0
  102. openhack/tools/ast_tools.py +307 -0
  103. openhack/tools/coverage.py +1078 -0
  104. openhack/tools/filesystem.py +404 -0
  105. openhack/tools/nextjs.py +258 -0
  106. openhack/tools/registry.py +52 -0
  107. openhack/tui.py +3450 -0
  108. openhack/updates.py +170 -0
  109. openhack-0.1.0.dist-info/METADATA +189 -0
  110. openhack-0.1.0.dist-info/RECORD +113 -0
  111. openhack-0.1.0.dist-info/WHEEL +4 -0
  112. openhack-0.1.0.dist-info/entry_points.txt +2 -0
  113. openhack-0.1.0.dist-info/licenses/LICENSE +661 -0
@@ -0,0 +1,131 @@
1
+ """
2
+ Supabase Auth, JWT, and service_role key exposure detection prompt.
3
+ """
4
+
5
+ SUPABASE_AUTH_PROMPT = """## Auth & JWT Security in Supabase
6
+
7
+ ### Pre-Computed Recon Available
8
+
9
+ The `supabase_recon` context already contains:
10
+ - `supabase_recon.clients` -- all Supabase client initializations with `uses_service_role` flag
11
+ - `supabase_recon.config.env_vars` -- Supabase-related environment variable names found in the codebase
12
+ - `supabase_recon.edge_functions` -- Edge Functions with `uses_service_role` flag
13
+
14
+ **Immediate critical findings:**
15
+ - Any entry in `clients` with `uses_service_role: True` in a client-side file (e.g., under `app/`, `pages/`, `components/`, or any file imported by client code)
16
+ - `NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY` in `config.env_vars` (service role key exposed to browser via Next.js public env var)
17
+
18
+ ### What to Look For
19
+
20
+ 1. **service_role key exposed in client code**
21
+ - The `service_role` key bypasses ALL RLS policies -- it's the master key
22
+ - Must NEVER appear in client-side code, browser bundles, or public env vars
23
+ - Check: `NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY`, `VITE_SUPABASE_SERVICE_ROLE_KEY`
24
+ - Check: `createClient(url, serviceRoleKey)` in files served to the browser
25
+
26
+ 2. **Hardcoded keys in source code**
27
+ - API keys, service role keys, or JWT secrets committed to the repository
28
+ - `grep` for long base64 strings near Supabase client initialization
29
+ - Check `.env.local`, `.env.example`, `.env` files for real key values
30
+
31
+ 3. **getSession() vs getUser() confusion**
32
+ - `getSession()` reads the session from local storage -- it is NOT authenticated by the server
33
+ - An attacker can forge the session in localStorage and `getSession()` will trust it
34
+ - `getUser()` makes a server call to verify the JWT -- this is the safe method
35
+ - Server-side code should always use `getUser()` for authorization decisions
36
+
37
+ 4. **JWT stored in localStorage**
38
+ - Tokens in localStorage are accessible to any JavaScript on the page (XSS-exfiltrable)
39
+ - Supabase stores tokens in localStorage by default
40
+ - If the app has any XSS vulnerability, auth tokens can be stolen
41
+
42
+ 5. **Missing audience/issuer verification**
43
+ - Custom endpoints that accept JWTs should verify `iss` (issuer) and `aud` (audience)
44
+ - Tokens from one Supabase project should not work on another
45
+ - Edge Functions should not trust the `Authorization` header without verification
46
+
47
+ 6. **apikey treated as identity**
48
+ - The `apikey` header identifies the project, NOT the user
49
+ - The anon key is public and project-scoped
50
+ - Code that checks for `apikey` presence as an auth mechanism is vulnerable
51
+
52
+ 7. **Refresh token mismanagement**
53
+ - Long-lived refresh tokens that never expire
54
+ - Refresh tokens not rotated after use
55
+ - Revocation not implemented for compromised tokens
56
+
57
+ ### Vulnerable Patterns
58
+
59
+ ```typescript
60
+ // VULNERABLE: service_role key in client-side code
61
+ import { createClient } from '@supabase/supabase-js'
62
+ const supabase = createClient(
63
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
64
+ process.env.NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY! // EXPOSED TO BROWSER!
65
+ )
66
+ ```
67
+
68
+ ```typescript
69
+ // VULNERABLE: Using getSession() for authorization
70
+ export async function GET() {
71
+ const { data: { session } } = await supabase.auth.getSession()
72
+ if (!session) return new Response('Unauthorized', { status: 401 })
73
+ // session could be forged from localStorage!
74
+ const userId = session.user.id // CANNOT BE TRUSTED
75
+ const data = await supabase.from('orders').select().eq('user_id', userId)
76
+ return Response.json(data)
77
+ }
78
+ ```
79
+
80
+ ```typescript
81
+ // VULNERABLE: Treating apikey as authentication
82
+ export async function middleware(req) {
83
+ const apiKey = req.headers.get('apikey')
84
+ if (!apiKey) return new Response('Unauthorized', { status: 401 })
85
+ // apikey is public! This is NOT authentication
86
+ }
87
+ ```
88
+
89
+ ### Safe Patterns
90
+
91
+ ```typescript
92
+ // SAFE: service_role key only in server-side code
93
+ // lib/supabase/admin.ts (never imported by client code)
94
+ import { createClient } from '@supabase/supabase-js'
95
+ export const supabaseAdmin = createClient(
96
+ process.env.SUPABASE_URL!, // No NEXT_PUBLIC_ prefix
97
+ process.env.SUPABASE_SERVICE_ROLE_KEY! // No NEXT_PUBLIC_ prefix
98
+ )
99
+ ```
100
+
101
+ ```typescript
102
+ // SAFE: Using getUser() for authorization
103
+ export async function GET() {
104
+ const { data: { user }, error } = await supabase.auth.getUser()
105
+ if (error || !user) return new Response('Unauthorized', { status: 401 })
106
+ // user.id is verified by the server
107
+ const data = await supabase.from('orders').select().eq('user_id', user.id)
108
+ return Response.json(data)
109
+ }
110
+ ```
111
+
112
+ ### Search Patterns
113
+
114
+ 1. Check `supabase_recon.clients` for `uses_service_role: True` -- inspect the file path to determine if client-side
115
+ 2. Check `supabase_recon.config.env_vars` for `NEXT_PUBLIC_*SERVICE_ROLE*` or `VITE_*SERVICE_ROLE*`
116
+ 3. `grep` for `service_role`, `serviceRole`, `SUPABASE_SERVICE_ROLE` in all files
117
+ 4. `grep` for `getSession` in server-side files (API routes, server components, middleware) -- should be `getUser`
118
+ 5. `grep` for `localStorage` near token storage/retrieval
119
+ 6. `grep` for hardcoded JWT-like strings (long base64 with dots: `eyJ...`)
120
+ 7. Check `.env*` files for real key values committed to repo
121
+
122
+ ### Severity Assessment
123
+
124
+ - **Critical**: service_role key exposed in client bundle or public env var
125
+ - **Critical**: Hardcoded service_role key in committed source code
126
+ - **High**: Using `getSession()` instead of `getUser()` for server-side authorization
127
+ - **High**: Custom endpoints accepting JWTs without audience/issuer verification
128
+ - **Medium**: JWT tokens stored in localStorage (XSS risk)
129
+ - **Medium**: apikey header used as authentication mechanism
130
+ - **Low**: Refresh token configuration concerns
131
+ """
@@ -0,0 +1,150 @@
1
+ """
2
+ Supabase Edge Functions security detection prompt.
3
+ """
4
+
5
+ SUPABASE_EDGE_FUNCTIONS_PROMPT = """## Edge Functions Security in Supabase
6
+
7
+ ### Pre-Computed Recon Available
8
+
9
+ The `supabase_recon` context already contains:
10
+ - `supabase_recon.edge_functions` -- all discovered Edge Functions with `uses_service_role`, `has_cors`, `has_auth_check`, `has_jwt_verification` flags
11
+
12
+ **Immediate findings: any Edge Function with `uses_service_role: True` + `has_auth_check: False` is high risk -- it has full database access but doesn't verify who's calling it.**
13
+
14
+ ### What to Look For
15
+
16
+ 1. **service_role usage without JWT verification**
17
+ - Edge Functions commonly initialize Supabase with `SUPABASE_SERVICE_ROLE_KEY` for admin operations
18
+ - If the function doesn't verify the caller's JWT, anyone can trigger admin-level database operations
19
+ - The function must extract and verify the JWT from the `Authorization` header
20
+
21
+ 2. **CORS misconfiguration**
22
+ - Wildcard `Access-Control-Allow-Origin: *` combined with `Access-Control-Allow-Credentials: true`
23
+ - Reflected `Origin` header in `Access-Control-Allow-Origin` without validation
24
+ - Missing CORS headers allowing any origin to call the function
25
+
26
+ 3. **SSRF via fetch()**
27
+ - Edge Functions can make outbound HTTP requests
28
+ - If the URL is controlled by user input, the function can be used to probe internal services
29
+ - Metadata service access: `http://169.254.169.254/` or cloud provider metadata endpoints
30
+
31
+ 4. **Secrets in error traces or logs**
32
+ - Error responses that include stack traces with environment variable values
33
+ - `console.log` or `console.error` dumping request/response bodies containing secrets
34
+ - Service role key or other secrets appearing in response bodies on error
35
+
36
+ 5. **Not re-deriving user from JWT**
37
+ - Trusting `user_id` or `tenant_id` from the request body instead of extracting from JWT
38
+ - The function should parse the JWT to get the authenticated user identity
39
+
40
+ 6. **Missing auth on function invocation**
41
+ - Edge Functions accessible without any Authorization header
42
+ - No validation that the caller is authenticated at all
43
+
44
+ ### Vulnerable Patterns
45
+
46
+ ```typescript
47
+ // supabase/functions/process-order/index.ts
48
+ // VULNERABLE: service_role with no auth check
49
+ import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
50
+
51
+ Deno.serve(async (req) => {
52
+ const { orderId, userId } = await req.json()
53
+
54
+ // Uses service_role (bypasses all RLS)
55
+ const supabase = createClient(
56
+ Deno.env.get('SUPABASE_URL')!,
57
+ Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
58
+ )
59
+
60
+ // Trusts client-supplied userId instead of extracting from JWT
61
+ const { data } = await supabase
62
+ .from('orders')
63
+ .update({ status: 'processed' })
64
+ .eq('id', orderId)
65
+ .eq('user_id', userId) // Client controls this!
66
+
67
+ return new Response(JSON.stringify(data))
68
+ })
69
+ ```
70
+
71
+ ```typescript
72
+ // VULNERABLE: Wildcard CORS with credentials
73
+ const corsHeaders = {
74
+ 'Access-Control-Allow-Origin': '*',
75
+ 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
76
+ 'Access-Control-Allow-Credentials': 'true', // Dangerous with wildcard origin!
77
+ }
78
+ ```
79
+
80
+ ```typescript
81
+ // VULNERABLE: SSRF via user-controlled URL
82
+ Deno.serve(async (req) => {
83
+ const { webhookUrl, data } = await req.json()
84
+ const response = await fetch(webhookUrl, { // User controls the URL!
85
+ method: 'POST',
86
+ body: JSON.stringify(data),
87
+ })
88
+ return new Response(JSON.stringify({ status: response.status }))
89
+ })
90
+ ```
91
+
92
+ ### Safe Patterns
93
+
94
+ ```typescript
95
+ // SAFE: Verify JWT before using service_role
96
+ import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
97
+
98
+ Deno.serve(async (req) => {
99
+ // Verify the caller's JWT
100
+ const authHeader = req.headers.get('Authorization')
101
+ if (!authHeader) {
102
+ return new Response('Missing authorization', { status: 401 })
103
+ }
104
+
105
+ // Create a user-context client to verify the token
106
+ const supabaseUser = createClient(
107
+ Deno.env.get('SUPABASE_URL')!,
108
+ Deno.env.get('SUPABASE_ANON_KEY')!,
109
+ { global: { headers: { Authorization: authHeader } } }
110
+ )
111
+
112
+ const { data: { user }, error } = await supabaseUser.auth.getUser()
113
+ if (error || !user) {
114
+ return new Response('Invalid token', { status: 401 })
115
+ }
116
+
117
+ // Now use service_role for admin operations, scoped to verified user
118
+ const supabaseAdmin = createClient(
119
+ Deno.env.get('SUPABASE_URL')!,
120
+ Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
121
+ )
122
+
123
+ const { data } = await supabaseAdmin
124
+ .from('orders')
125
+ .update({ status: 'processed' })
126
+ .eq('user_id', user.id) // user.id from verified JWT
127
+
128
+ return new Response(JSON.stringify(data))
129
+ })
130
+ ```
131
+
132
+ ### Search Patterns
133
+
134
+ 1. Check `supabase_recon.edge_functions` for `uses_service_role: True` + `has_auth_check: False`
135
+ 2. Read each Edge Function file (`supabase/functions/*/index.ts`)
136
+ 3. `grep` for `SUPABASE_SERVICE_ROLE_KEY`, `Deno.env.get`, `createClient`
137
+ 4. `grep` for `Access-Control-Allow-Origin` and CORS patterns
138
+ 5. `grep` for `fetch(` with dynamic URLs (SSRF risk)
139
+ 6. Check for `Authorization` header extraction and JWT verification
140
+ 7. Look for `req.json()` fields used directly as user identity
141
+
142
+ ### Severity Assessment
143
+
144
+ - **Critical**: Edge Function with service_role and no auth, performing data mutations
145
+ - **High**: Edge Function trusting client-supplied user IDs with service_role
146
+ - **High**: SSRF via user-controlled fetch URLs
147
+ - **Medium**: CORS wildcard with credentials
148
+ - **Medium**: Secrets leaked in error responses
149
+ - **Low**: Missing rate limiting on function invocation
150
+ """
@@ -0,0 +1,102 @@
1
+ """
2
+ Supabase GraphQL security detection prompt.
3
+ """
4
+
5
+ SUPABASE_GRAPHQL_PROMPT = """## GraphQL Security in Supabase
6
+
7
+ ### Pre-Computed Recon Available
8
+
9
+ The `supabase_recon` context already contains:
10
+ - `supabase_recon.graphql.introspection_enabled` -- whether introspection queries succeed as anon
11
+ - `supabase_recon.graphql.types_count` -- number of types exposed
12
+ - `supabase_recon.schema` -- PostgREST schema (compare with GraphQL for parity drift)
13
+
14
+ **Key insight: Supabase GraphQL (pg_graphql) sits on top of Postgres with RLS. However, enforcement can drift between REST and GraphQL paths, and nested queries can expose data that direct queries wouldn't.**
15
+
16
+ ### What to Look For
17
+
18
+ 1. **Introspection enabled in production**
19
+ - Introspection queries reveal the entire schema: tables, columns, types, relations
20
+ - Check `supabase_recon.graphql.introspection_enabled`
21
+ - While Supabase enables this by default, it exposes the attack surface
22
+
23
+ 2. **Nested relation queries bypassing per-row checks**
24
+ - GraphQL allows deep nesting: `{ users { orders { payments { ... } } } }`
25
+ - RLS is applied per-table, but nested resolvers may not re-check ownership at each level
26
+ - If `users` has RLS but `orders` doesn't, querying through `users->orders` may leak order data
27
+
28
+ 3. **Global node IDs reusable across viewers**
29
+ - pg_graphql provides global `nodeId` fields for relay-style pagination
30
+ - If one user discovers a `nodeId`, they might query it directly as a different user
31
+ - The `nodeId` encodes table and primary key, allowing targeted access
32
+
33
+ 4. **REST vs GraphQL enforcement drift**
34
+ - Protections applied at the REST/PostgREST level may not exist in the GraphQL path
35
+ - Column restrictions, computed fields, or custom logic may differ between the two
36
+ - Test the same query via both REST and GraphQL to check parity
37
+
38
+ 5. **Deep nesting for resource exhaustion**
39
+ - Deeply nested queries can cause expensive joins
40
+ - No built-in query depth limiting in pg_graphql
41
+
42
+ ### Vulnerable Patterns
43
+
44
+ ```graphql
45
+ # VULNERABLE: Introspection reveals full schema
46
+ {
47
+ __schema {
48
+ types {
49
+ name
50
+ fields {
51
+ name
52
+ type { name }
53
+ }
54
+ }
55
+ }
56
+ }
57
+ ```
58
+
59
+ ```graphql
60
+ # VULNERABLE: Deep nested query may bypass per-row checks
61
+ {
62
+ usersCollection {
63
+ edges {
64
+ node {
65
+ email
66
+ ordersCollection {
67
+ edges {
68
+ node {
69
+ amount
70
+ paymentsCollection {
71
+ edges {
72
+ node {
73
+ cardLast4
74
+ billingAddress
75
+ }
76
+ }
77
+ }
78
+ }
79
+ }
80
+ }
81
+ }
82
+ }
83
+ }
84
+ }
85
+ ```
86
+
87
+ ### Search Patterns
88
+
89
+ 1. Check `supabase_recon.graphql.introspection_enabled` for immediate finding
90
+ 2. `grep` app code for `graphql`, `/graphql/v1`, `gql` template tags
91
+ 3. Compare `supabase_recon.schema.tables` (REST) with GraphQL introspection types
92
+ 4. Look for nested query patterns in application code
93
+ 5. For targeted probing: use `supabase_graphql_query` with deep nested queries on tables that have RLS gaps
94
+
95
+ ### Severity Assessment
96
+
97
+ - **Critical**: Nested GraphQL queries expose sensitive data that REST + RLS would block
98
+ - **High**: REST vs GraphQL enforcement drift allowing bypass via GraphQL path
99
+ - **Medium**: Introspection enabled revealing sensitive schema structure
100
+ - **Medium**: Global node IDs allowing cross-user targeted access
101
+ - **Low**: Deep nesting without depth limits (DoS potential)
102
+ """
@@ -0,0 +1,99 @@
1
+ """
2
+ Supabase PostgREST and REST API vulnerability detection prompt.
3
+ """
4
+
5
+ SUPABASE_POSTGREST_PROMPT = """## PostgREST / REST API Vulnerabilities in Supabase
6
+
7
+ ### Pre-Computed Recon Available
8
+
9
+ The `supabase_recon` context already contains:
10
+ - `supabase_recon.anon_access` -- which tables the anon role can SELECT/INSERT/UPDATE/DELETE
11
+ - `supabase_recon.schema.columns` -- column names per table (from OpenAPI spec)
12
+ - `supabase_recon.schema.tables` -- all tables exposed via PostgREST
13
+
14
+ **Start by reviewing `anon_access` for tables with unexpected access permissions.** Any table where anon can read or write is a potential finding.
15
+
16
+ ### What to Look For
17
+
18
+ 1. **IDOR via direct row access**
19
+ - Tables accessible by anon where rows can be fetched by ID
20
+ - No ownership filter means any row is accessible: `/rest/v1/orders?id=eq.<any_id>`
21
+ - Check if `supabase_recon.query_patterns` shows `.from('table').select()` without `.eq('user_id', ...)`
22
+
23
+ 2. **Filter abuse for data extraction**
24
+ - `or` filters: `?or=(user_id.eq.X,user_id.is.null)` to bypass intended scoping
25
+ - `ilike` filters: `?email=ilike.*@company.com` for wildcard enumeration
26
+ - `neq` filters: `?role=neq.admin` to probe for admin rows
27
+ - Combine filters to extract data column by column
28
+
29
+ 3. **Relation embedding overfetch**
30
+ - PostgREST allows embedding related tables: `?select=*,profile(*),orders(*)`
31
+ - If the parent table has RLS but the embedded table doesn't, data leaks through the join
32
+ - Check for foreign key relationships between protected and unprotected tables
33
+
34
+ 4. **Mass assignment via PATCH/POST**
35
+ - If anon or authenticated users can INSERT/UPDATE, they may modify unintended columns
36
+ - Example: setting `role`, `is_admin`, `org_id` via PATCH when only `name` was intended
37
+ - Check if the app uses RPC functions for writes instead of direct table access
38
+
39
+ 5. **Count-based blind enumeration**
40
+ - `Prefer: count=exact` header returns total row count even with `limit=0`
41
+ - Can enumerate total records in a table without reading actual data
42
+ - Use `supabase_query_table` with `count: "exact"` to test
43
+
44
+ 6. **Schema exposure**
45
+ - PostgREST OpenAPI spec reveals table names, column names, types, and foreign keys
46
+ - `supabase_recon.schema` already has this data -- check for sensitive column names
47
+
48
+ ### Vulnerable Application Code Patterns
49
+
50
+ ```typescript
51
+ // VULNERABLE: No ownership filter, any user can read all orders
52
+ const { data } = await supabase
53
+ .from('orders')
54
+ .select('*')
55
+
56
+ // VULNERABLE: Client-controlled filter that can be bypassed
57
+ const { data } = await supabase
58
+ .from('users')
59
+ .select('*')
60
+ .eq('id', params.id) // User controls the ID
61
+ ```
62
+
63
+ ```typescript
64
+ // VULNERABLE: Embedding exposes related data
65
+ const { data } = await supabase
66
+ .from('posts')
67
+ .select('*, author:users(*), comments(*)')
68
+ // If users table has weaker RLS than posts, author data leaks
69
+ ```
70
+
71
+ ### Safe Patterns
72
+
73
+ ```typescript
74
+ // SAFE: Server-side ownership filter using authenticated user
75
+ const { data: { user } } = await supabase.auth.getUser()
76
+ const { data } = await supabase
77
+ .from('orders')
78
+ .select('*')
79
+ .eq('user_id', user.id)
80
+ ```
81
+
82
+ ### Search Patterns
83
+
84
+ 1. Check `supabase_recon.anon_access` for tables with `select: True` or `insert: True`
85
+ 2. Check `supabase_recon.schema.columns` for sensitive column names (email, password, ssn, token, secret)
86
+ 3. `grep` app code for `.from(` calls without subsequent `.eq('user_id'` or ownership filters
87
+ 4. Look for `select('*,` patterns that embed related tables
88
+ 5. For targeted probing: use `supabase_query_table` with filter combinations to test data extraction
89
+ 6. For write testing: use `supabase_mutate_table` to test mass assignment
90
+
91
+ ### Severity Assessment
92
+
93
+ - **Critical**: Anon can read sensitive data (PII, financial) from tables with no RLS
94
+ - **High**: Anon can write to tables (INSERT/UPDATE), enabling data manipulation
95
+ - **High**: Filter abuse allows extraction of data that should be scoped per-user
96
+ - **Medium**: Schema exposure reveals sensitive table/column structure
97
+ - **Medium**: Count-based enumeration reveals record counts
98
+ - **Low**: Non-sensitive public data accessible (may be intentional)
99
+ """
@@ -0,0 +1,93 @@
1
+ """
2
+ Supabase Realtime security detection prompt.
3
+ """
4
+
5
+ SUPABASE_REALTIME_PROMPT = """## Realtime Security in Supabase
6
+
7
+ ### Pre-Computed Recon Available
8
+
9
+ The `supabase_recon` context already contains:
10
+ - `supabase_recon.anon_access` -- tables accessible by anon (if anon can SELECT, realtime changes likely also leak)
11
+ - `supabase_recon.rls_policies` -- RLS status per table (realtime respects RLS for postgres_changes)
12
+
13
+ **Key insight: Realtime postgres_changes subscriptions are governed by the same RLS policies as direct table access. If a table has no RLS or permissive RLS, realtime will leak every change to any subscriber.**
14
+
15
+ ### What to Look For
16
+
17
+ 1. **Subscriptions to tables without RLS**
18
+ - `supabase.channel('*').on('postgres_changes', { table: 'orders' }, ...)` on a table without RLS
19
+ - Every INSERT/UPDATE/DELETE on that table is broadcast to all subscribers including anon
20
+ - Cross-reference: if `anon_access[table].select` is True, realtime changes are also exposed
21
+
22
+ 2. **Broadcast/presence channels without authentication**
23
+ - Broadcast and presence channels don't go through RLS
24
+ - Channel names derived from guessable IDs: `room:${userId}`, `org:${orgId}`
25
+ - Any client can join any channel name and receive/send messages
26
+
27
+ 3. **Sensitive data in realtime payloads**
28
+ - Even with RLS, the `NEW` and `OLD` records in change events include all columns
29
+ - If the table has sensitive columns (passwords, tokens, secrets), they leak via realtime
30
+ - Check if the subscription uses column filtering
31
+
32
+ 4. **Cross-room join/publish**
33
+ - Broadcast channels: client can publish to any channel they can join
34
+ - If channel names encode authorization (e.g., `admin-updates`), any client can subscribe
35
+
36
+ ### Vulnerable Patterns
37
+
38
+ ```typescript
39
+ // VULNERABLE: Subscribing to table without RLS
40
+ supabase
41
+ .channel('orders-changes')
42
+ .on('postgres_changes',
43
+ { event: '*', schema: 'public', table: 'orders' },
44
+ (payload) => {
45
+ console.log('Change received:', payload)
46
+ // Receives ALL changes to orders table from ALL users
47
+ }
48
+ )
49
+ .subscribe()
50
+ ```
51
+
52
+ ```typescript
53
+ // VULNERABLE: Guessable channel name without auth verification
54
+ supabase
55
+ .channel(`user:${otherUserId}`) // Can join any user's channel
56
+ .on('broadcast', { event: 'notification' }, (payload) => {
57
+ // Receives another user's notifications
58
+ })
59
+ .subscribe()
60
+ ```
61
+
62
+ ### Safe Patterns
63
+
64
+ ```typescript
65
+ // SAFE: Table has proper RLS, so only authorized changes are received
66
+ // (Assuming orders table has: USING (auth.uid() = user_id) policy)
67
+ supabase
68
+ .channel('my-orders')
69
+ .on('postgres_changes',
70
+ { event: '*', schema: 'public', table: 'orders',
71
+ filter: `user_id=eq.${user.id}` },
72
+ (payload) => {
73
+ // Only receives changes for current user's orders
74
+ }
75
+ )
76
+ .subscribe()
77
+ ```
78
+
79
+ ### Search Patterns
80
+
81
+ 1. Check `supabase_recon.rls_policies.tables_without_rls` -- any table subscribed to via realtime is vulnerable
82
+ 2. `grep` app code for `.channel(`, `.on('postgres_changes'`, `.on('broadcast'`, `.subscribe(`
83
+ 3. Check channel name patterns for guessable IDs
84
+ 4. Cross-reference subscribed tables with `supabase_recon.anon_access`
85
+ 5. Look for `filter:` parameter in postgres_changes subscriptions (reduces exposure but doesn't replace RLS)
86
+
87
+ ### Severity Assessment
88
+
89
+ - **Critical**: Realtime subscription on sensitive table without RLS, leaking all changes to anon
90
+ - **High**: Broadcast/presence channels with guessable names exposing user-specific data
91
+ - **Medium**: Realtime subscription includes sensitive columns even with RLS
92
+ - **Low**: Realtime on intentionally public data (e.g., public chat, live scores)
93
+ """
@@ -0,0 +1,110 @@
1
+ """
2
+ Supabase Row Level Security (RLS) misconfiguration detection prompt.
3
+ """
4
+
5
+ SUPABASE_RLS_PROMPT = """## Row Level Security (RLS) Misconfigurations in Supabase
6
+
7
+ ### Pre-Computed Recon Available
8
+
9
+ The `supabase_recon` context already contains the results of deterministic RLS analysis:
10
+ - `supabase_recon.rls_policies.tables_without_rls` -- tables that have NO RLS enabled in migrations
11
+ - `supabase_recon.rls_policies.tables` -- all tables with their RLS status and policy details
12
+ - `supabase_recon.anon_access` -- runtime test results showing which tables the anon role can actually read/write
13
+
14
+ **Start by cross-referencing these two datasets.** A table with no RLS in migrations AND `select: True` in `anon_access` is an instant critical finding.
15
+
16
+ ### What to Look For
17
+
18
+ 1. **Tables without RLS enabled**
19
+ - Every non-public table must have `ALTER TABLE ... ENABLE ROW LEVEL SECURITY`
20
+ - Absence means the table is wide open to any role with access
21
+
22
+ 2. **Overly permissive policies**
23
+ - `using (true)` or `with check (true)` grants unrestricted access
24
+ - Policies that don't reference `auth.uid()` or a tenant column
25
+
26
+ 3. **Per-operation gaps**
27
+ - SELECT policy exists but UPDATE/DELETE/INSERT policies are missing
28
+ - A table may be read-protected but write-open (or vice versa)
29
+
30
+ 4. **Policies trusting client-supplied data**
31
+ - Policy checks `user_id` column (which can be set by the client on INSERT) instead of `auth.uid()` from the JWT
32
+ - Example: `using (user_id = auth.uid())` on SELECT is fine, but `with check (user_id = auth.uid())` on INSERT trusts whatever the client sends as `user_id` if not also constrained
33
+
34
+ 5. **Missing tenant/org constraints**
35
+ - Multi-tenant apps must scope every policy to `org_id`/`tenant_id`
36
+ - Missing tenant constraint allows cross-tenant data access
37
+
38
+ 6. **Complex join inference**
39
+ - Even with RLS, count-based queries (`Prefer: count=exact`) can leak information about rows the user shouldn't see
40
+ - Policies applied after filters can allow inference via response timing or counts
41
+
42
+ ### Vulnerable Migration Patterns
43
+
44
+ ```sql
45
+ -- VULNERABLE: Table created with NO RLS
46
+ CREATE TABLE orders (
47
+ id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
48
+ user_id uuid REFERENCES auth.users(id),
49
+ amount numeric,
50
+ status text
51
+ );
52
+ -- Missing: ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
53
+ -- Missing: CREATE POLICY ... ON orders ...
54
+ ```
55
+
56
+ ```sql
57
+ -- VULNERABLE: RLS enabled but permit-all policy
58
+ ALTER TABLE documents ENABLE ROW LEVEL SECURITY;
59
+ CREATE POLICY "allow_all" ON documents FOR ALL USING (true);
60
+ ```
61
+
62
+ ```sql
63
+ -- VULNERABLE: SELECT policy exists, but no INSERT/UPDATE/DELETE policies
64
+ ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
65
+ CREATE POLICY "users_read_own" ON profiles FOR SELECT USING (auth.uid() = id);
66
+ -- No INSERT/UPDATE/DELETE policies = default deny, but could be intentionally missing
67
+ -- or could be a gap if the app needs write access
68
+ ```
69
+
70
+ ```sql
71
+ -- VULNERABLE: Policy trusts client-supplied user_id on INSERT
72
+ CREATE POLICY "users_insert" ON posts FOR INSERT
73
+ WITH CHECK (user_id = auth.uid());
74
+ -- If user_id is in the INSERT payload, client can set user_id = auth.uid() trivially
75
+ -- But what about other columns like org_id, role, etc.?
76
+ ```
77
+
78
+ ### Safe Patterns
79
+
80
+ ```sql
81
+ -- SAFE: Proper RLS with per-operation policies
82
+ ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
83
+ CREATE POLICY "users_read_own_orders" ON orders FOR SELECT
84
+ USING (auth.uid() = user_id);
85
+ CREATE POLICY "users_insert_own_orders" ON orders FOR INSERT
86
+ WITH CHECK (auth.uid() = user_id);
87
+ CREATE POLICY "users_update_own_orders" ON orders FOR UPDATE
88
+ USING (auth.uid() = user_id);
89
+ CREATE POLICY "users_delete_own_orders" ON orders FOR DELETE
90
+ USING (auth.uid() = user_id);
91
+ ```
92
+
93
+ ### Search Patterns
94
+
95
+ 1. Check `supabase_recon.rls_policies.tables_without_rls` for immediate findings
96
+ 2. Cross-reference with `supabase_recon.anon_access` to confirm runtime exposure
97
+ 3. `grep` migration files for `using (true)` and `with check (true)`
98
+ 4. Check policies for `auth.uid()` references -- policies without it are suspicious
99
+ 5. Look for tables with SELECT policy but missing UPDATE/DELETE/INSERT policies
100
+ 6. For targeted probing: use `supabase_query_table` with filters like `or=(user_id.eq.X,user_id.is.null)` to test policy enforcement
101
+
102
+ ### Severity Assessment
103
+
104
+ - **Critical**: Table without RLS + anon can read/write data at runtime (confirmed by `anon_access`)
105
+ - **Critical**: Sensitive table (users, payments, PII) with `using (true)` policy
106
+ - **High**: RLS enabled but policy is overly permissive or missing for some operations
107
+ - **High**: Policy trusts client-supplied columns instead of JWT context
108
+ - **Medium**: Missing tenant isolation in multi-tenant app
109
+ - **Low**: RLS gap on non-sensitive, intentionally public data
110
+ """