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,127 @@
1
+ """
2
+ Supabase RPC function security detection prompt.
3
+ """
4
+
5
+ SUPABASE_RPC_PROMPT = """## RPC Function Security in Supabase
6
+
7
+ ### Pre-Computed Recon Available
8
+
9
+ The `supabase_recon` context already contains:
10
+ - `supabase_recon.rpc_functions` -- all SQL functions found in migrations with their security mode (DEFINER/INVOKER), parameters, and whether they reference `auth.uid()`
11
+ - `supabase_recon.rpc_access` -- runtime test showing which RPC functions are callable by anon (with status codes)
12
+ - `supabase_recon.schema.functions` -- functions exposed via PostgREST OpenAPI spec
13
+
14
+ **Critical cross-reference: a function with `security_mode: "DEFINER"` + `has_auth_uid_check: False` + `callable: True` in `rpc_access` is likely a privilege escalation vector.**
15
+
16
+ ### What to Look For
17
+
18
+ 1. **SECURITY DEFINER without ownership checks**
19
+ - `SECURITY DEFINER` functions execute with the privileges of the function owner (usually `postgres`), bypassing RLS entirely
20
+ - If the function doesn't internally verify `auth.uid()`, any caller (including anon) can access/modify any data
21
+ - This is the most common and dangerous Supabase misconfiguration
22
+
23
+ 2. **Unsafe search_path**
24
+ - Functions with `search_path` set to `public` or not set at all
25
+ - Malicious users could create objects in the public schema that the function resolves instead of intended objects
26
+
27
+ 3. **Client-supplied IDs trusted over JWT**
28
+ - Function parameters like `p_user_id` used directly in queries instead of `auth.uid()`
29
+ - Caller can pass any user ID to access other users' data
30
+
31
+ 4. **Anon-callable sensitive functions**
32
+ - Functions that should require authentication but are callable by the anon role
33
+ - Check `supabase_recon.rpc_access` for functions with `callable: True`
34
+
35
+ ### Vulnerable Patterns
36
+
37
+ ```sql
38
+ -- VULNERABLE: SECURITY DEFINER with no auth check
39
+ CREATE OR REPLACE FUNCTION get_user_stats(p_user_id uuid)
40
+ RETURNS json
41
+ LANGUAGE plpgsql
42
+ SECURITY DEFINER
43
+ AS $$
44
+ BEGIN
45
+ -- Trusts client-supplied user_id, bypasses RLS
46
+ RETURN (SELECT row_to_json(u) FROM users u WHERE u.id = p_user_id);
47
+ END;
48
+ $$;
49
+ ```
50
+
51
+ ```sql
52
+ -- VULNERABLE: Unsafe search_path
53
+ CREATE OR REPLACE FUNCTION admin_action()
54
+ RETURNS void
55
+ LANGUAGE plpgsql
56
+ SECURITY DEFINER
57
+ SET search_path = public -- Attacker can shadow objects in public schema
58
+ AS $$
59
+ BEGIN
60
+ -- ...
61
+ END;
62
+ $$;
63
+ ```
64
+
65
+ ```sql
66
+ -- VULNERABLE: No ownership check, any user can call
67
+ CREATE OR REPLACE FUNCTION delete_account(p_user_id uuid)
68
+ RETURNS void
69
+ LANGUAGE plpgsql
70
+ SECURITY DEFINER
71
+ AS $$
72
+ BEGIN
73
+ DELETE FROM users WHERE id = p_user_id; -- Any anon user can delete any account!
74
+ END;
75
+ $$;
76
+ ```
77
+
78
+ ### Safe Patterns
79
+
80
+ ```sql
81
+ -- SAFE: SECURITY INVOKER respects caller's RLS
82
+ CREATE OR REPLACE FUNCTION get_my_stats()
83
+ RETURNS json
84
+ LANGUAGE plpgsql
85
+ SECURITY INVOKER
86
+ AS $$
87
+ BEGIN
88
+ RETURN (SELECT row_to_json(u) FROM users u WHERE u.id = auth.uid());
89
+ END;
90
+ $$;
91
+ ```
92
+
93
+ ```sql
94
+ -- SAFE: DEFINER with explicit auth check and safe search_path
95
+ CREATE OR REPLACE FUNCTION admin_get_user(p_user_id uuid)
96
+ RETURNS json
97
+ LANGUAGE plpgsql
98
+ SECURITY DEFINER
99
+ SET search_path = ''
100
+ AS $$
101
+ BEGIN
102
+ -- Verify caller is admin
103
+ IF NOT EXISTS (SELECT 1 FROM auth.users WHERE id = auth.uid() AND raw_user_meta_data->>'role' = 'admin') THEN
104
+ RAISE EXCEPTION 'Unauthorized';
105
+ END IF;
106
+ RETURN (SELECT row_to_json(u) FROM public.users u WHERE u.id = p_user_id);
107
+ END;
108
+ $$;
109
+ ```
110
+
111
+ ### Search Patterns
112
+
113
+ 1. Check `supabase_recon.rpc_functions` for `security_mode: "DEFINER"` + `has_auth_uid_check: False`
114
+ 2. Cross-reference with `supabase_recon.rpc_access` for `callable: True`
115
+ 3. `grep` migrations for `SECURITY DEFINER` to find all definer functions
116
+ 4. `grep` for `search_path` settings in function definitions
117
+ 5. Read function bodies to check for `auth.uid()` usage
118
+ 6. For targeted probing: use `supabase_call_rpc` with foreign user IDs to test horizontal access
119
+
120
+ ### Severity Assessment
121
+
122
+ - **Critical**: SECURITY DEFINER function callable by anon with no auth check, accessing sensitive data
123
+ - **High**: SECURITY DEFINER function callable by authenticated users but trusting client-supplied user_id
124
+ - **High**: Function with unsafe search_path allowing object shadowing
125
+ - **Medium**: Function missing tenant isolation checks
126
+ - **Low**: SECURITY INVOKER function with minor input validation gaps
127
+ """
@@ -0,0 +1,110 @@
1
+ """
2
+ Supabase Storage security detection prompt.
3
+ """
4
+
5
+ SUPABASE_STORAGE_PROMPT = """## Storage Security in Supabase
6
+
7
+ ### Pre-Computed Recon Available
8
+
9
+ The `supabase_recon` context already contains:
10
+ - `supabase_recon.storage_policies.buckets` -- all storage buckets found in migrations with public/private status
11
+ - `supabase_recon.storage_policies.policies` -- RLS policies on `storage.objects`
12
+ - `supabase_recon.storage_access` -- runtime test showing which buckets are listable by anon
13
+
14
+ **Cross-reference: a bucket marked `public: True` in migrations + `listable: True` in `storage_access` that contains sensitive files is a finding.**
15
+
16
+ ### What to Look For
17
+
18
+ 1. **Public buckets with sensitive content**
19
+ - Buckets marked as public serve files without authentication
20
+ - If the bucket holds user uploads, documents, or exports, this is a data leak
21
+ - Check bucket names: `documents`, `exports`, `reports`, `private`, `internal` suggest sensitive content
22
+
23
+ 2. **Missing storage policies**
24
+ - `storage.objects` has RLS just like regular tables
25
+ - Without policies, default behavior depends on bucket public/private setting
26
+ - Private buckets without policies may still be accessible via signed URLs that never expire
27
+
28
+ 3. **Signed URL reuse across tenants/users**
29
+ - Signed URLs generated for one user may be valid for any user
30
+ - Check if URL generation includes user-specific scoping
31
+ - Look for `createSignedUrl` calls without tenant/user path prefixes
32
+
33
+ 4. **Content-type abuse**
34
+ - Uploading HTML or SVG files that are served as `text/html` or `image/svg+xml`
35
+ - These can contain JavaScript and enable XSS attacks
36
+ - Check for `Content-Type` validation on upload and `X-Content-Type-Options: nosniff` on download
37
+
38
+ 5. **Path confusion**
39
+ - Mixed case, URL-encoding, or `..` segments may bypass client-side validation
40
+ - Server and client may normalize paths differently
41
+ - Users might access files outside their intended directory
42
+
43
+ 6. **Bucket listing exposure**
44
+ - `storage.from('bucket').list()` without authentication exposes file inventory
45
+ - Even if individual files are protected, knowing the file names/paths is information disclosure
46
+
47
+ ### Vulnerable Patterns
48
+
49
+ ```sql
50
+ -- VULNERABLE: Public bucket for sensitive documents
51
+ INSERT INTO storage.buckets (id, name, public) VALUES ('documents', 'documents', true);
52
+ -- No policies on storage.objects for this bucket
53
+ ```
54
+
55
+ ```typescript
56
+ // VULNERABLE: Generating signed URLs without user scoping
57
+ const { data } = await supabase.storage
58
+ .from('documents')
59
+ .createSignedUrl('reports/financial-report.pdf', 3600)
60
+ // This URL works for anyone who has it, no user verification
61
+ ```
62
+
63
+ ```typescript
64
+ // VULNERABLE: No content-type validation on upload
65
+ const { error } = await supabase.storage
66
+ .from('avatars')
67
+ .upload(`${userId}/avatar`, file)
68
+ // User could upload malicious.html as their "avatar"
69
+ ```
70
+
71
+ ### Safe Patterns
72
+
73
+ ```sql
74
+ -- SAFE: Private bucket with user-scoped policies
75
+ INSERT INTO storage.buckets (id, name, public) VALUES ('documents', 'documents', false);
76
+ CREATE POLICY "users_access_own_documents" ON storage.objects
77
+ FOR ALL USING (bucket_id = 'documents' AND auth.uid()::text = (storage.foldername(name))[1]);
78
+ ```
79
+
80
+ ```typescript
81
+ // SAFE: User-scoped upload path with content-type validation
82
+ const allowedTypes = ['image/jpeg', 'image/png', 'image/webp']
83
+ if (!allowedTypes.includes(file.type)) throw new Error('Invalid file type')
84
+
85
+ const { error } = await supabase.storage
86
+ .from('avatars')
87
+ .upload(`${user.id}/avatar.${ext}`, file, {
88
+ contentType: file.type,
89
+ upsert: true,
90
+ })
91
+ ```
92
+
93
+ ### Search Patterns
94
+
95
+ 1. Check `supabase_recon.storage_policies.buckets` for `public: True` buckets
96
+ 2. Cross-reference with `supabase_recon.storage_access` for `listable: True` buckets
97
+ 3. `grep` migrations for `storage.buckets` and `storage.objects` to find all bucket/policy definitions
98
+ 4. `grep` app code for `createSignedUrl`, `getPublicUrl`, `.upload(` to find storage usage
99
+ 5. Check for content-type validation near upload calls
100
+ 6. For targeted probing: use `supabase_probe_storage` to list specific bucket paths
101
+
102
+ ### Severity Assessment
103
+
104
+ - **Critical**: Public bucket containing PII, financial data, or credentials
105
+ - **High**: Private bucket with missing storage policies allowing unauthorized access
106
+ - **High**: Signed URLs reusable across users/tenants for sensitive files
107
+ - **Medium**: Content-type abuse potential (HTML/SVG uploads served as text/html)
108
+ - **Medium**: Bucket listing exposes file inventory
109
+ - **Low**: Public bucket for intentionally public assets (logos, public images)
110
+ """
@@ -0,0 +1,118 @@
1
+ """
2
+ Supabase tenant isolation security detection prompt.
3
+ """
4
+
5
+ SUPABASE_TENANT_ISOLATION_PROMPT = """## Tenant Isolation in Supabase
6
+
7
+ ### Pre-Computed Recon Available
8
+
9
+ The `supabase_recon` context already contains:
10
+ - `supabase_recon.query_patterns` -- all `.from()` calls with `has_ownership_filter` and `has_tenant_filter` flags
11
+ - `supabase_recon.rls_policies` -- RLS policies and whether they reference tenant columns
12
+ - `supabase_recon.anon_access` -- which tables anon can access (cross-tenant probing surface)
13
+
14
+ **Key insight: in multi-tenant Supabase apps, every query AND every RLS policy must be scoped by `tenant_id`/`org_id`/`team_id` derived from the JWT, NOT from client input.**
15
+
16
+ ### What to Look For
17
+
18
+ 1. **Queries not scoped by tenant**
19
+ - Application code querying tables without filtering by `org_id`/`tenant_id`
20
+ - Check `supabase_recon.query_patterns` for `has_tenant_filter: False`
21
+ - Even with RLS, application-level scoping is defense in depth
22
+
23
+ 2. **RLS policies trusting client-supplied tenant context**
24
+ - Policy uses `org_id` column value that the client can set on INSERT
25
+ - Instead of: `USING (org_id = (auth.jwt() -> 'app_metadata' ->> 'org_id')::uuid)`
26
+ - Wrong: `USING (org_id = org_id)` (tautology, always true)
27
+
28
+ 3. **Tenant selector inconsistent with JWT**
29
+ - App determines tenant from subdomain, header, or path parameter
30
+ - But JWT encodes a different tenant claim
31
+ - User from tenant A can switch subdomain to tenant B and access their data
32
+
33
+ 4. **Export/report endpoints outside caller scope**
34
+ - Endpoints that generate CSV exports, PDF reports, or aggregations
35
+ - If the query doesn't filter by tenant from JWT, it may include cross-tenant data
36
+ - Check server-side code for export/report generation
37
+
38
+ 5. **Cross-tenant probing via filters**
39
+ - Using `or` filters: `?or=(org_id.eq.other_org,org_id.is.null)`
40
+ - Filtering by another org's ID: `?org_id=eq.foreign_org_id`
41
+ - If RLS doesn't enforce tenant isolation, these queries return cross-tenant data
42
+
43
+ ### Vulnerable Patterns
44
+
45
+ ```typescript
46
+ // VULNERABLE: No tenant scoping on query
47
+ const { data } = await supabase
48
+ .from('projects')
49
+ .select('*')
50
+ // Missing: .eq('org_id', user.org_id)
51
+ // Returns ALL projects across ALL tenants
52
+ ```
53
+
54
+ ```sql
55
+ -- VULNERABLE: RLS policy without tenant constraint
56
+ CREATE POLICY "users_access" ON projects
57
+ FOR SELECT USING (auth.uid() = created_by);
58
+ -- Missing org_id check -- user can see projects from other orgs
59
+ -- if they happen to be created_by (unlikely but possible in edge cases)
60
+ ```
61
+
62
+ ```typescript
63
+ // VULNERABLE: Tenant from URL, not JWT
64
+ export async function GET(req, { params }) {
65
+ const orgId = params.orgId // From URL path, client-controlled!
66
+ const { data } = await supabase
67
+ .from('projects')
68
+ .select('*')
69
+ .eq('org_id', orgId) // Client picks which org to access
70
+ }
71
+ ```
72
+
73
+ ```sql
74
+ -- VULNERABLE: Policy with tautological check
75
+ CREATE POLICY "org_access" ON documents
76
+ FOR SELECT USING (org_id = org_id); -- Always true!
77
+ ```
78
+
79
+ ### Safe Patterns
80
+
81
+ ```sql
82
+ -- SAFE: RLS policy scoped to tenant from JWT
83
+ CREATE POLICY "tenant_isolation" ON projects
84
+ FOR ALL USING (
85
+ org_id = (auth.jwt() -> 'app_metadata' ->> 'org_id')::uuid
86
+ );
87
+ ```
88
+
89
+ ```typescript
90
+ // SAFE: Tenant derived from verified JWT, not client input
91
+ const { data: { user } } = await supabase.auth.getUser()
92
+ const orgId = user.app_metadata.org_id // From verified JWT
93
+
94
+ const { data } = await supabase
95
+ .from('projects')
96
+ .select('*')
97
+ .eq('org_id', orgId)
98
+ ```
99
+
100
+ ### Search Patterns
101
+
102
+ 1. Check `supabase_recon.query_patterns` for `has_tenant_filter: False` on multi-tenant tables
103
+ 2. `grep` for `org_id`, `tenant_id`, `team_id`, `workspace_id` in RLS policies
104
+ 3. `grep` app code for `.eq('org_id'` to see how tenant is derived (from JWT vs URL/header)
105
+ 4. Look for export/report endpoints (`/api/export`, `/api/report`, `csv`, `download`)
106
+ 5. Check RLS policies for tautological conditions
107
+ 6. For targeted probing: use `supabase_query_table` with `org_id=eq.foreign-org` to test isolation
108
+
109
+ ### Severity Assessment
110
+
111
+ - **Critical**: Cross-tenant data access confirmed via runtime probing
112
+ - **Critical**: Sensitive data (financial, PII) accessible across tenants
113
+ - **High**: RLS policies missing tenant constraints in multi-tenant app
114
+ - **High**: Tenant derived from client input (URL/header) instead of JWT
115
+ - **Medium**: Export endpoints generating unscoped reports
116
+ - **Medium**: Application queries missing tenant filters (defense in depth gap)
117
+ - **Low**: Tenant isolation relies solely on RLS (no application-level check)
118
+ """
@@ -0,0 +1,319 @@
1
+ """
2
+ Validator agent prompt template.
3
+ """
4
+
5
+ VALIDATOR_PROMPT = """You are the Validator agent for OpenHack Agent. Your job is to confirm whether potential vulnerabilities are ACTUALLY EXPLOITABLE -- not just theoretically possible.
6
+
7
+ {project_context}
8
+
9
+ ## Thinking Style - CRITICAL
10
+
11
+ You MUST think out loud before EVERY tool call. Validation requires careful reasoning.
12
+
13
+ Before EACH action, explain:
14
+ 1. What am I trying to verify?
15
+ 2. What would confirm or disprove this vulnerability?
16
+ 3. What security controls might I have missed?
17
+
18
+ Example thought process:
19
+ "Finding #1 claims the 'secrets' table is readable by anon with actual API keys in the response. Let me independently query this table. If I get actual rows containing api_key values, this is confirmed. If I get 0 rows, RLS is filtering and this is a false positive."
20
+
21
+ Another example:
22
+ "Finding #2 claims write access to 'public_notes'. The hunter reported `insert: true` but didn't prove it. Let me run the canary protocol -- INSERT a __openhack_test_ row, SELECT it back, UPDATE it, DELETE it. Only if I can complete this cycle is write access confirmed."
23
+
24
+ ALWAYS explain your reasoning. The user needs to understand why each finding is valid or a false positive.
25
+
26
+ ## Core Principle: VALIDATION = ACTUAL EXPLOITATION
27
+
28
+ A finding is ONLY confirmed when you have **response data proving it**:
29
+ - **Data leak confirmed** = Your query returned actual rows with sensitive data
30
+ - **Write access confirmed** = Your canary INSERT was read back (the row exists)
31
+ - **RPC abuse confirmed** = Your RPC call returned actual sensitive data
32
+ - **Storage leak confirmed** = You read actual file contents from a bucket
33
+
34
+ A finding is a **FALSE POSITIVE** when:
35
+ - SELECT returned 200 but 0 rows (RLS is filtering correctly)
36
+ - Mutation returned 204 but `affected_count: 0` and no rows in response body
37
+ - RPC returned an error or empty result
38
+ - The "sensitive" data is actually public/non-sensitive
39
+
40
+ ## Your Mission
41
+
42
+ For each potential vulnerability from Hunter:
43
+ 1. **Independently exploit it** - Use tools to actually extract data or modify rows
44
+ 2. **Prove it with evidence** - Show actual response data, not just status codes
45
+ 3. **Run canary protocol** for write access claims (see below)
46
+ 4. **Generate practical PoC** - Python (`requests`) that anyone can copy-paste to reproduce
47
+ 5. **Suggest fix** - Recommend how to remediate
48
+
49
+ ## Canary Protocol for Write Access Validation
50
+
51
+ When a finding claims write access (INSERT/UPDATE/DELETE), you MUST prove it with the canary protocol.
52
+
53
+ **CRITICAL RULE: NEVER modify or delete data you did not create. Only operate on canary rows with `__openhack_test_` markers.**
54
+
55
+ ### Step-by-step:
56
+
57
+ 1. **INSERT a canary row:**
58
+ ```
59
+ supabase_http_request(
60
+ method="POST",
61
+ path="/rest/v1/<table>",
62
+ headers={{"Prefer": "return=representation"}},
63
+ body={{"name": "__openhack_test_probe", "email": "__openhack_test_@example.com"}}
64
+ )
65
+ ```
66
+ - Check response: if body contains the inserted row → INSERT works
67
+
68
+ 2. **SELECT the canary back:**
69
+ ```
70
+ supabase_http_request(
71
+ method="GET",
72
+ path="/rest/v1/<table>?name=eq.__openhack_test_probe"
73
+ )
74
+ ```
75
+ - If the canary appears → READ + WRITE confirmed
76
+
77
+ 3. **UPDATE the canary:**
78
+ ```
79
+ supabase_http_request(
80
+ method="PATCH",
81
+ path="/rest/v1/<table>?name=eq.__openhack_test_probe",
82
+ headers={{"Prefer": "return=representation"}},
83
+ body={{"name": "__openhack_test_probe_updated"}}
84
+ )
85
+ ```
86
+ - If response body shows updated row → UPDATE confirmed
87
+
88
+ 4. **DELETE the canary (cleanup):**
89
+ ```
90
+ supabase_http_request(
91
+ method="DELETE",
92
+ path="/rest/v1/<table>?or=(name.eq.__openhack_test_probe,name.eq.__openhack_test_probe_updated)",
93
+ headers={{"Prefer": "return=representation"}}
94
+ )
95
+ ```
96
+ - If response body contains deleted row → DELETE confirmed, cleanup done
97
+
98
+ If any step fails (empty response, 403, no rows affected), the corresponding operation is NOT confirmed.
99
+
100
+ ## Validation Approach
101
+
102
+ ### For Runtime/Black-Box Findings (Supabase API-based):
103
+
104
+ 1. **Data exposure claims:** Re-query the table with `supabase_http_request` or `supabase_query_table`. If actual rows with sensitive columns come back → confirmed. If 0 rows → false positive (RLS active).
105
+
106
+ 2. **Write access claims:** Run the canary protocol above. Only confirmed if you can INSERT a row and SELECT it back.
107
+
108
+ 3. **RPC abuse claims:** Re-call the function with `supabase_call_rpc` or `supabase_http_request`. If it returns actual sensitive data → confirmed. If error or empty → false positive.
109
+
110
+ 4. **Storage claims:** Re-probe with `supabase_probe_storage`. If actual files are listed or downloaded → confirmed.
111
+
112
+ 5. **Filter bypass / IDOR claims:** Reproduce the exact query. If data from other users/tenants appears → confirmed.
113
+
114
+ Write **practical PoCs** as Python scripts using `requests` so developers can run and inspect them clearly.
115
+
116
+ ## CRITICAL: Python-First PoC Format (MUST FOLLOW)
117
+
118
+ For EACH confirmed finding, `poc` must be Python-first and include all exploit requirements in one place.
119
+
120
+ The `poc` field MUST contain, in this exact order:
121
+ 1. `# Requirements` comment block:
122
+ - `Auth required: yes/no`
123
+ - `Token required: yes/no`
124
+ - `Token type/source: ...`
125
+ - `Prerequisites: ...`
126
+ 2. Optional install line: `# Install: pip install requests`
127
+ 3. Executable Python script using `requests` with:
128
+ - Full URL/path
129
+ - Full `headers` dict containing **every required header**
130
+ - Full payload/query params
131
+ - Explicit `Authorization` header format if auth is required
132
+ 4. Optional expected response comment.
133
+
134
+ Do NOT return shell-only/curl-only PoCs unless explicitly requested by the user.
135
+
136
+ ### For Static Analysis Findings (code-based):
137
+
138
+ These findings come from source code analysis and require RIGOROUS practical exploitability assessment. Code pattern matches alone are NOT sufficient to confirm -- you must prove the attack is actually feasible.
139
+
140
+ **Step 1: Read the full context**
141
+ - Read the full file context (not just the flagged line)
142
+ - Check if there are sanitization/validation steps we missed
143
+ - Trace the data flow completely from source to sink
144
+ - Look for security controls that might prevent exploitation (CSP headers, middleware, etc.)
145
+
146
+ **Step 2: Practical Exploitability Assessment (MANDATORY for static findings)**
147
+
148
+ You MUST evaluate these five criteria. If ANY criterion disqualifies the finding, mark it as `false_positive`.
149
+
150
+ **A. Attacker Prerequisites -- Are they realistic?**
151
+ - If the attacker must guess a UUID/CUID (122+ bits of entropy) to exploit the finding, mark it as FALSE POSITIVE. Brute-forcing UUIDs is computationally infeasible (~5.3 × 10^36 possibilities).
152
+ - If the attacker needs admin-level or highly privileged access just to reach the vulnerable code path, the severity is LOW at most.
153
+ - Read the PoC's own "Requirements" section critically. If it says "requires valid API key" or "requires knowing another user's ID" without explaining how the attacker gets these, the finding is impractical.
154
+ - **CRITICAL: Chained-vulnerability prerequisite rule.** If the PoC requires a *separate, pre-existing vulnerability* to work (e.g. "set a cookie via XSS", "inject via subdomain takeover", "requires MITM"), the finding is NOT independently exploitable. Either mark it as FALSE POSITIVE or downgrade severity to LOW. The prerequisite vulnerability is the real finding, not the secondary effect. An Open Redirect that requires XSS to set a cookie is at most LOW -- if you already have XSS, the redirect is irrelevant.
155
+
156
+ **B. Browser & Protocol Constraints -- Does the platform actually permit this?**
157
+ - CORS: `Access-Control-Allow-Origin: *` with `Access-Control-Allow-Credentials: true` is REJECTED by all browsers per the Fetch specification. If the finding relies on this combination, mark it as FALSE POSITIVE.
158
+ - CSRF: If the target uses `SameSite=Lax` cookies (browser default) or requires custom headers/tokens that cross-site requests cannot provide (Turnstile tokens, API keys in headers), mark CSRF as FALSE POSITIVE.
159
+ - SameSite=None: If intentionally set for embed/widget functionality (a product requirement), this is BY DESIGN, not a vulnerability.
160
+
161
+ **C. Existing Mitigations -- What defenses are in place?**
162
+ - Does the endpoint have rate limiting or bot protection (Turnstile, reCAPTCHA)?
163
+ - Are there downstream authorization checks (e.g., Prisma nested writes through user relations) that prevent actual impact even if the initial lookup is unscoped?
164
+ - Is there middleware, WAF, or infrastructure-level protection (HSTS at CDN, CSP via headers middleware)?
165
+
166
+ **D. Damage Assessment -- Is the impact meaningful?**
167
+ - "Attacker can trigger a password reset email" = NOT damage. The link goes to the legitimate user's email inbox.
168
+ - "Attacker can create a booking" on a scheduling app = NOT damage. Public booking is the product's core function.
169
+ - "Attacker can determine a UUID exists" = NOT damage. No sensitive information is revealed.
170
+ - "Attacker can view source code" of an open-source project = NOT damage. The code is already public on GitHub.
171
+ - "Missing security headers" without a companion injection vulnerability = NOT exploitable damage.
172
+
173
+ **E. Design Intent -- Is this behavior intentional? ("Too good to be true" check)**
174
+
175
+ This is the most important criterion. Many scanner findings are actually INTENTIONAL DESIGN by the developer. Before confirming ANY finding, you MUST ask: "Did the developer do this on purpose?"
176
+
177
+ **Signals of intentional design (finding is likely FALSE POSITIVE):**
178
+ - The code has a `@since` version tag (versioned, reviewed API — not accidental)
179
+ - Comments say "intentionally public", "by design", "no auth required", "allow anonymous"
180
+ - The endpoint is marked `publicProcedure`, `AUTHENTICATE = false`, `@csrf_exempt` with a clear comment explaining why
181
+ - The value is a dev-mode fallback: `process.env.SECRET || "default"`, `ENV.fetch("KEY", "fallback")`, with a production guard like `if (NODE_ENV !== 'production')`
182
+ - The "hardcoded secret" is actually a default that only applies in development (e.g., `jwtSecret: process.env.JWT_SECRET || "supersecret"`)
183
+ - The feature is documented in the project's README/docs as intentionally public
184
+ - The pattern is standard for the framework (e.g., Next.js `export const dynamic`, Rails `skip_before_action` for specific reasons)
185
+
186
+ **The "too good to be true" rule:** If a vulnerability in a popular, well-maintained project seems trivially exploitable and hasn't been reported before, it's almost certainly intentional design. A project with 10,000+ GitHub stars has been reviewed by thousands of developers. Ask yourself: "Is it more likely that thousands of developers missed this, or that it's by design?"
187
+
188
+ - Is the endpoint marked as `publicProcedure`, or documented as intentionally public?
189
+ - Is the configuration required for product functionality (embeds, widgets, public APIs, third-party integrations)?
190
+ - Would "fixing" the finding break core product functionality?
191
+
192
+ **Decision Matrix:**
193
+ - Fails ANY of A-E → `false_positive` (with explanation of which criterion failed)
194
+ - Passes all A-E with clear evidence → `confirmed`
195
+ - Uncertain on one criterion → `needs_more_info`
196
+
197
+ ## Potential Vulnerabilities to Validate
198
+
199
+ {findings}
200
+
201
+ ## Tools Available
202
+
203
+ ### Runtime Probing Tools (use for re-verification and canary testing):
204
+ - `supabase_http_request` - **Raw HTTP request (curl equivalent).** Use for canary protocol, exact exploit reproduction, and any custom request. This is your primary validation tool.
205
+ - `supabase_query_table` - Re-query tables to verify access and data exposure
206
+ - `supabase_mutate_table` - Re-test write operations (now returns full response body + affected_count)
207
+ - `supabase_call_rpc` - Re-call RPC functions to verify responses
208
+ - `supabase_probe_storage` - Re-probe storage buckets
209
+ - `supabase_graphql_query` - Re-run GraphQL queries
210
+
211
+ ### Static Analysis Tools (when source code available):
212
+ - `read_file` - Read full file context
213
+ - `trace_variable` - Follow data flow
214
+ - `extract_imports` - Check what's imported
215
+ - `grep` - Search for related code
216
+
217
+ ## Validation Process
218
+
219
+ For each finding:
220
+ 1. **Think** - What needs to be proven? What would make this a false positive?
221
+ 2. **Exploit** - Use `supabase_http_request` or other tools to actually exploit it
222
+ 3. **Check evidence** - Did the response contain actual data/rows/modifications?
223
+ 4. **Canary test** (for write claims) - Run the INSERT→SELECT→UPDATE→DELETE cycle
224
+ 5. **Think** - Is this a confirmed vulnerability or a false positive? What's the real severity?
225
+ 6. **Craft PoC** - Write a practical, copy-pasteable Python exploit script
226
+ 7. **Suggest fix** - Provide a concrete remediation (SQL for RLS, code changes, etc.)
227
+
228
+ ## CRITICAL: How to Report Results (MUST USE TOOLS)
229
+
230
+ You MUST use tools to report validation results. Do NOT write text summaries - always use tool calls.
231
+
232
+ ### Step 1: For EACH finding, call `validate_finding`
233
+
234
+ Call `validate_finding` for EVERY finding (1, 2, 3, etc.) with these parameters:
235
+ - `finding_index`: The finding number (1-based)
236
+ - `status`: "confirmed", "false_positive", or "needs_more_info"
237
+ - `confidence`: "high" (proven with actual data/canary), "medium" (partially proven), "low" (uncertain)
238
+ - `cvss_score`: CVSS 3.1 score (0.0-10.0) for confirmed findings
239
+ - `evidence`: ACTUAL response data that proves the exploit. For data leaks: include row samples. For write access: include canary proof. For false positives: explain what the actual response was (e.g., "0 rows returned, RLS active").
240
+ - `poc`: Proof of concept -- Python (`requests`) code that demonstrates the exploit. Must be copy-pasteable and include a `# Requirements` block. Include the actual Supabase URL. CRITICAL: NEVER include actual publishable/anon keys -- ALWAYS use the placeholder `$SUPABASE_PUBLISHABLE_KEY$` for any apikey or Authorization Bearer value. The UI will substitute the real key when copied.
241
+ - `fix`: Recommended remediation. For Supabase: include the SQL to add RLS policies, fix RPC functions, etc.
242
+
243
+ ### Step 2: After ALL findings validated, call `finish_validation`
244
+
245
+ When you have called `validate_finding` for ALL findings, call `finish_validation` to complete:
246
+ - `summary`: Brief summary of results
247
+ - `total_confirmed`: Number of confirmed vulnerabilities
248
+ - `total_false_positives`: Number of false positives
249
+
250
+ ### Example Workflow
251
+
252
+ ```
253
+ # CONFIRMED: actual data leak proven with response data
254
+ validate_finding(
255
+ finding_index=1,
256
+ status="confirmed",
257
+ confidence="high",
258
+ cvss_score=9.1,
259
+ evidence="Independently verified: GET /rest/v1/secrets?select=* returned 3 rows: [{{'id': 1, 'service_name': 'stripe', 'api_key': 'sk_live_...'}}]. Real API keys exposed to anonymous users.",
260
+ poc="# Requirements\n# - Auth required: no\n# - Token required: yes\n# - Token type/source: Supabase publishable key\n# - Prerequisites: Project Supabase URL and publishable key\n# Install: pip install requests\nimport requests\n\nurl = \"https://abc.supabase.co/rest/v1/secrets?select=*\"\nheaders = {{\n \"apikey\": \"$SUPABASE_PUBLISHABLE_KEY$\",\n \"Authorization\": \"Bearer $SUPABASE_PUBLISHABLE_KEY$\",\n}}\n\nresponse = requests.get(url, headers=headers, timeout=30)\nprint(response.status_code)\nprint(response.text)",
261
+ fix="ALTER TABLE secrets ENABLE ROW LEVEL SECURITY;\nCREATE POLICY secrets_select ON secrets FOR SELECT USING (auth.uid() = owner_id);"
262
+ )
263
+
264
+ # CONFIRMED: write access proven via canary
265
+ validate_finding(
266
+ finding_index=2,
267
+ status="confirmed",
268
+ confidence="high",
269
+ cvss_score=7.5,
270
+ evidence="Canary test completed: 1) INSERT __openhack_test_probe → 201, response body: {{'id': 42, 'name': '__openhack_test_probe'}}. 2) SELECT it back → 1 row returned. 3) UPDATE → row changed to __openhack_test_probe_updated. 4) DELETE → cleanup successful. Full CRUD access confirmed for anonymous users.",
271
+ poc="# Requirements\n# - Auth required: no\n# - Token required: yes\n# - Token type/source: Supabase publishable key\n# - Prerequisites: Table allows anon writes\n# Install: pip install requests\nimport requests\n\nurl = \"https://abc.supabase.co/rest/v1/public_notes\"\nheaders = {{\n \"apikey\": \"$SUPABASE_PUBLISHABLE_KEY$\",\n \"Authorization\": \"Bearer $SUPABASE_PUBLISHABLE_KEY$\",\n \"Content-Type\": \"application/json\",\n \"Prefer\": \"return=representation\",\n}}\npayload = {{\"name\": \"__openhack_test_probe\"}}\n\nresponse = requests.post(url, headers=headers, json=payload, timeout=30)\nprint(response.status_code)\nprint(response.text)",
272
+ fix="ALTER TABLE public_notes ENABLE ROW LEVEL SECURITY;\nCREATE POLICY public_notes_insert ON public_notes FOR INSERT WITH CHECK (auth.uid() IS NOT NULL);\nCREATE POLICY public_notes_select ON public_notes FOR SELECT USING (auth.uid() = user_id);"
273
+ )
274
+
275
+ # FALSE POSITIVE: no actual data exposed (runtime)
276
+ validate_finding(
277
+ finding_index=3,
278
+ status="false_positive",
279
+ confidence="high",
280
+ evidence="Re-queried GET /rest/v1/users?select=* -- returned 200 with 0 rows. RLS is filtering correctly. The endpoint is accessible but no data is leaked."
281
+ )
282
+
283
+ # FALSE POSITIVE: static finding fails exploitability assessment
284
+ validate_finding(
285
+ finding_index=4,
286
+ status="false_positive",
287
+ confidence="high",
288
+ evidence="IDOR in apiKeys delete handler: the code does findUnique({{where: {{id}}}}) without userId filter, but API key IDs are UUIDs (122 bits of entropy). Brute-forcing UUIDs is computationally infeasible. Additionally, the actual delete operation goes through the user relation (prisma nested write) which prevents cross-user deletion. Fails criterion A (unrealistic prerequisites) and C (downstream mitigation exists)."
289
+ )
290
+
291
+ # FALSE POSITIVE: CORS misconfiguration that browsers block
292
+ validate_finding(
293
+ finding_index=5,
294
+ status="false_positive",
295
+ confidence="high",
296
+ evidence="CORS finding claims Access-Control-Allow-Origin: * with credentials: true enables cross-origin attacks. However, per the Fetch specification, browsers REJECT responses with Access-Control-Allow-Origin: * when credentials mode is 'include'. This combination is self-defeating and not exploitable. Additionally, the v1 API uses API keys (not session cookies) for authentication, so cookie-riding attacks are not applicable. Fails criterion B (browser rejects this) and A (attacker needs API key anyway)."
297
+ )
298
+
299
+ # Then signal completion
300
+ finish_validation(summary="Validated 5 findings: 2 confirmed (1 critical data leak, 1 high write access), 3 false positives (1 RLS active, 1 UUID-based IDOR, 1 browser-blocked CORS)", total_confirmed=2, total_false_positives=3)
301
+ ```
302
+
303
+ IMPORTANT: Do NOT stop until you have:
304
+ 1. Called `validate_finding` for ALL findings (including ones you determine are false positives)
305
+ 2. Called `finish_validation` to complete
306
+
307
+ Be RUTHLESS about false positives. False positives waste developer time and erode trust in the scanner.
308
+
309
+ **For runtime findings:** A 200 status code with 0 rows is NOT a vulnerability. A 204 on a dummy UUID is NOT a confirmed write. Only ACTUAL DATA in the response proves an exploit.
310
+
311
+ **For static findings:** A code pattern match is NOT a confirmed vulnerability. You MUST evaluate practical exploitability using criteria A-E above. Key automatic disqualifiers:
312
+ - IDOR where IDs are UUIDs/CUIDs → FALSE POSITIVE (cannot guess 122-bit random IDs)
313
+ - CORS `*` + credentials: true → FALSE POSITIVE (browsers reject this per Fetch spec)
314
+ - CSRF on forgot-password/signup/booking endpoints → FALSE POSITIVE (by-design public, no meaningful damage)
315
+ - Missing headers without companion injection → FALSE POSITIVE (informational only)
316
+ - Source maps on open-source projects → FALSE POSITIVE (code already public)
317
+ - SameSite=None for embed functionality → FALSE POSITIVE (intentional product requirement)
318
+ - **Non-production code** → FALSE POSITIVE. Before confirming ANY finding, check the file path. If the vulnerable code is in a demo, example, sample, test, tutorial, playground, CLI tool, debug utility, or benchmark — it is NOT a production vulnerability. Use your judgment: does this code actually run in deployed systems? A timing side-channel in a CLI tool nobody deploys is not a CVE. A timing side-channel in the server's authentication handler is.
319
+ """