supascan 0.0.9 → 0.1.0

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 (3) hide show
  1. package/README.md +60 -110
  2. package/dist/supascan.js +66 -83
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -1,145 +1,88 @@
1
1
  # supascan
2
2
 
3
- A security analysis CLI tool for Supabase databases that helps identify exposed data, analyze schemas, and test RPC functions.
3
+ [![.github/workflows/tests.yml](https://github.com/abhishekg999/supascan/actions/workflows/tests.yml/badge.svg)](https://github.com/abhishekg999/supascan/actions/workflows/tests.yml) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/abhishekg999/supascan/master/LICENCE)
4
4
 
5
- ## Installation
5
+ **supascan** is an automated security scanner for Supabase databases. It detects exposed data, analyzes Row Level Security (RLS) policies, tests RPC functions, and generates comprehensive security reports.
6
6
 
7
- ```bash
8
- bun install -g supascan
9
- ```
7
+ ## Features
10
8
 
11
- ## Usage
9
+ - Automated schema and table discovery
10
+ - RLS policy effectiveness testing
11
+ - Exposed data detection with row count estimation
12
+ - RPC function parameter analysis and testing
13
+ - JWT token decoding and validation
14
+ - Multiple output formats (Console, JSON, HTML)
15
+ - Interactive HTML reports with live query interface
16
+ - Credential extraction from JavaScript files (experimental)
12
17
 
13
- ### Basic Analysis
18
+ ## Installation
14
19
 
15
- Analyze your Supabase database for security issues:
20
+ **NPM:**
16
21
 
17
22
  ```bash
18
- supascan --url https://your-project.supabase.co --key your-anon-key
23
+ npm install -g supascan
19
24
  ```
20
25
 
21
- ### Available Commands
22
-
23
- #### Database Analysis
26
+ **Bun:**
24
27
 
25
28
  ```bash
26
- # Analyze all schemas
27
- supascan --url <url> --key <key>
28
-
29
- # Analyze specific schema
30
- supascan --url <url> --key <key> --schema public
31
-
32
- # Generate HTML report
33
- supascan --url <url> --key <key> --html
34
-
35
- # JSON output
36
- supascan --url <url> --key <key> --json
29
+ bun install -g supascan
37
30
  ```
38
31
 
39
- #### Data Dumping
32
+ **From source:**
40
33
 
41
34
  ```bash
42
- # Dump table data
43
- supascan --url <url> --key <key> --dump public.users --limit 100
44
-
45
- # Dump Swagger JSON for schema
46
- supascan --url <url> --key <key> --dump public
35
+ git clone https://github.com/abhishekg999/supascan.git
36
+ cd supascan
37
+ bun install
38
+ bun run build
47
39
  ```
48
40
 
49
- #### RPC Testing
50
-
51
- ```bash
52
- # Get RPC help
53
- supascan --url <url> --key <key> --rpc public.get_user_stats
54
-
55
- # Call RPC with parameters
56
- supascan --url <url> --key <key> --rpc public.get_user_stats --args '{"user_id": "123"}'
57
-
58
- # Show query execution plan
59
- supascan --url <url> --key <key> --rpc public.get_user_stats --args '{"user_id": "123"}' --explain
60
- ```
41
+ ## Usage
61
42
 
62
- #### Credential Extraction (Experimental)
43
+ To get basic options and usage:
63
44
 
64
45
  ```bash
65
- # Extract credentials from JS file
66
- supascan --extract https://example.com/app.js --url <url> --key <key>
46
+ supascan --help
67
47
  ```
68
48
 
69
- ## Options
70
-
71
- | Option | Description |
72
- | ---------------------------------- | ---------------------------------------------------------------- |
73
- | `-u, --url <url>` | Supabase URL |
74
- | `-k, --key <key>` | Supabase anon key |
75
- | `-s, --schema <schema>` | Schema to analyze (default: all schemas) |
76
- | `-x, --extract <url>` | Extract credentials from JS file URL (experimental) |
77
- | `--dump <schema.table\|schema>` | Dump data from specific table or swagger JSON from schema |
78
- | `--limit <number>` | Limit rows for dump or RPC results (default: 10) |
79
- | `--rpc <schema.rpc_name>` | Call an RPC function (read-only operations only) |
80
- | `--args <json>` | JSON arguments for RPC call (use $VAR for environment variables) |
81
- | `--json` | Output as JSON |
82
- | `--html` | Generate HTML report |
83
- | `-d, --debug` | Enable debug mode |
84
- | `--explain` | Show query execution plan |
85
- | `--suppress-experimental-warnings` | Suppress experimental warnings |
86
-
87
- ## What supascan Analyzes
49
+ ### Quick Start
88
50
 
89
- ### Database Security Assessment
90
-
91
- - **Schema Discovery**: Automatically discovers all available schemas
92
- - **Table Access Analysis**: Identifies which tables are:
93
- - ✅ **Readable** - Data is exposed and accessible
94
- - ⚠️ **Empty/Protected** - No data or protected by RLS
95
- - ❌ **Denied** - Access is explicitly denied
96
-
97
- ### JWT Token Analysis
98
-
99
- - Parses and displays JWT token information
100
- - Shows issuer, audience, expiration, and role information
101
-
102
- ### RPC Function Analysis
103
-
104
- - Lists all available RPC functions
105
- - Shows parameter requirements and types
106
- - Validates parameters before execution
107
-
108
- ### Output Formats
109
-
110
- - **Console**: Colorized terminal output with detailed analysis
111
- - **JSON**: Machine-readable output for scripting
112
- - **HTML**: Visual report that opens in your browser
113
-
114
- ## Examples
51
+ ```bash
52
+ # Basic security scan
53
+ supascan --url https://your-project.supabase.co --key your-anon-key
115
54
 
116
- ### Security Analysis Report
55
+ # Generate HTML report
56
+ supascan --url https://your-project.supabase.co --key your-anon-key --html
117
57
 
118
- ```bash
119
- supascan --url https://abc123.supabase.co --key eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... --html
120
- ```
58
+ # Analyze specific schema
59
+ supascan --url https://your-project.supabase.co --key your-anon-key --schema public
121
60
 
122
- ### Check Specific Table Access
61
+ # Dump table data
62
+ supascan --url https://your-project.supabase.co --key your-anon-key --dump public.users --limit 100
123
63
 
124
- ```bash
125
- supascan --url https://abc123.supabase.co --key eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... --dump public.users --limit 5
64
+ # Test RPC function
65
+ supascan --url https://your-project.supabase.co --key your-anon-key --rpc public.my_function --args '{"param": "value"}'
126
66
  ```
127
67
 
128
- ### Test RPC Function
68
+ ## What supascan Detects
129
69
 
130
- ```bash
131
- supascan --url https://abc123.supabase.co --key eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... --rpc public.get_user_count --args '{"active": true}'
132
- ```
70
+ - **Exposed Tables**: Tables readable without authentication or with weak RLS
71
+ - **Data Leakage**: Estimated row counts for accessible tables
72
+ - **RPC Vulnerabilities**: Publicly callable functions and their parameters
73
+ - **JWT Issues**: Token expiration, role assignments, and claims
74
+ - **Schema Information**: Complete database structure visibility
133
75
 
134
76
  ## Security Considerations
135
77
 
136
- ⚠️ **Important**: This tool is designed for security analysis and testing. Only use it on:
78
+ ⚠️ **Important**: This tool is for authorized security testing only.
137
79
 
138
- - Your own databases
139
- - Databases you have explicit permission to test
140
- - Staging/development environments
80
+ - Only scan databases you own or have explicit permission to test
81
+ - Use on staging/development environments when possible
82
+ - Never use on production databases without proper authorization
83
+ - Be aware that scanning may trigger rate limits or monitoring alerts
141
84
 
142
- Never use this tool on production databases without proper authorization.
85
+ Unauthorized database scanning may be illegal in your jurisdiction.
143
86
 
144
87
  ## Development
145
88
 
@@ -150,12 +93,19 @@ bun install
150
93
  # Run locally
151
94
  bun run start
152
95
 
96
+ # Run tests
97
+ bun test
98
+
153
99
  # Build
154
100
  bun run build
101
+ ```
155
102
 
156
- # Test
157
- bun test
103
+ ## License
158
104
 
159
- # Lint
160
- bun run lint
161
- ```
105
+ supascan is distributed under the [MIT License](LICENCE).
106
+
107
+ ## Links
108
+
109
+ - **Homepage**: https://github.com/abhishekg999/supascan
110
+ - **Issues**: https://github.com/abhishekg999/supascan/issues
111
+ - **NPM**: https://www.npmjs.com/package/supascan
package/dist/supascan.js CHANGED
@@ -13931,19 +13931,24 @@ var openInBrowser = (filePath) => {
13931
13931
  const platform = process.platform;
13932
13932
  let command;
13933
13933
  let args;
13934
- switch (platform) {
13935
- case "darwin":
13936
- command = "open";
13937
- args = [filePath];
13938
- break;
13939
- case "win32":
13940
- command = "start";
13941
- args = [filePath];
13942
- break;
13943
- default:
13944
- command = "xdg-open";
13945
- args = [filePath];
13946
- break;
13934
+ if (Bun.env.BROWSER) {
13935
+ command = Bun.env.BROWSER;
13936
+ args = [filePath];
13937
+ } else {
13938
+ switch (platform) {
13939
+ case "darwin":
13940
+ command = "open";
13941
+ args = [filePath];
13942
+ break;
13943
+ case "win32":
13944
+ command = "start";
13945
+ args = [filePath];
13946
+ break;
13947
+ default:
13948
+ command = "xdg-open";
13949
+ args = [filePath];
13950
+ break;
13951
+ }
13947
13952
  }
13948
13953
  spawn(command, args, { detached: true, stdio: "ignore" });
13949
13954
  };
@@ -14062,11 +14067,22 @@ class SupabaseService {
14062
14067
  }
14063
14068
  const hasData = data && data.length > 0;
14064
14069
  if (hasData) {
14065
- log.debug(ctx, `Table ${table} is readable with data (EXPOSED)`);
14066
- return ok({ status: "readable", accessible: true, hasData: true });
14070
+ const { count } = await ctx.client.schema(schema).from(table).select("*", { count: "estimated", head: true });
14071
+ log.debug(ctx, `Table ${table} is readable with ~${count ?? "unknown"} rows (EXPOSED)`);
14072
+ return ok({
14073
+ status: "readable",
14074
+ accessible: true,
14075
+ hasData: true,
14076
+ rowCount: count ?? undefined
14077
+ });
14067
14078
  }
14068
14079
  log.debug(ctx, `Table ${table} returned 0 rows (empty or RLS blocked)`);
14069
- return ok({ status: "empty", accessible: true, hasData: false });
14080
+ return ok({
14081
+ status: "empty",
14082
+ accessible: true,
14083
+ hasData: false,
14084
+ rowCount: 0
14085
+ });
14070
14086
  }
14071
14087
  static async testTablesRead(ctx, schema, tables) {
14072
14088
  log.debug(ctx, `Testing read access for ${tables.length} tables`);
@@ -14255,15 +14271,11 @@ function toggleApiKey() {
14255
14271
 
14256
14272
  async function saveReport() {
14257
14273
  try {
14258
- // Generate filename with timestamp and domain
14259
14274
  const domain = '${domain}';
14260
- const timestamp = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
14275
+ const timestamp = new Date().toISOString().split('T')[0];
14261
14276
  const filename = \`supabase-analysis-\${domain}-\${timestamp}.html\`;
14262
-
14263
- // Get the current HTML content
14264
14277
  const htmlContent = document.documentElement.outerHTML;
14265
14278
 
14266
- // Check if File System Access API is supported
14267
14279
  if ('showSaveFilePicker' in window) {
14268
14280
  const fileHandle = await window.showSaveFilePicker({
14269
14281
  suggestedName: filename,
@@ -14278,11 +14290,8 @@ async function saveReport() {
14278
14290
  const writable = await fileHandle.createWritable();
14279
14291
  await writable.write(htmlContent);
14280
14292
  await writable.close();
14281
-
14282
- // Show success message
14283
14293
  showNotification('Report saved successfully!', 'success');
14284
14294
  } else {
14285
- // Fallback for browsers that don't support File System Access API
14286
14295
  const blob = new Blob([htmlContent], { type: 'text/html' });
14287
14296
  const url = URL.createObjectURL(blob);
14288
14297
  const a = document.createElement('a');
@@ -14296,17 +14305,13 @@ async function saveReport() {
14296
14305
  showNotification('Report downloaded successfully!', 'success');
14297
14306
  }
14298
14307
  } catch (error) {
14299
- if (error.name === 'AbortError') {
14300
- // User cancelled the save dialog
14301
- return;
14302
- }
14308
+ if (error.name === 'AbortError') return;
14303
14309
  console.error('Error saving report:', error);
14304
14310
  showNotification('Failed to save report: ' + error.message, 'error');
14305
14311
  }
14306
14312
  }
14307
14313
 
14308
14314
  function showNotification(message, type = 'info') {
14309
- // Create notification element
14310
14315
  const notification = document.createElement('div');
14311
14316
  notification.className = \`fixed top-4 right-4 px-4 py-3 rounded-lg shadow-lg z-50 font-mono text-sm transition-all duration-300 transform translate-x-full\`;
14312
14317
 
@@ -14321,17 +14326,10 @@ function showNotification(message, type = 'info') {
14321
14326
  notification.textContent = message;
14322
14327
  document.body.appendChild(notification);
14323
14328
 
14324
- // Animate in
14325
- setTimeout(() => {
14326
- notification.classList.remove('translate-x-full');
14327
- }, 100);
14328
-
14329
- // Remove after 3 seconds
14329
+ setTimeout(() => notification.classList.remove('translate-x-full'), 100);
14330
14330
  setTimeout(() => {
14331
14331
  notification.classList.add('translate-x-full');
14332
- setTimeout(() => {
14333
- document.body.removeChild(notification);
14334
- }, 300);
14332
+ setTimeout(() => document.body.removeChild(notification), 300);
14335
14333
  }, 3000);
14336
14334
  }
14337
14335
 
@@ -14342,18 +14340,13 @@ function escapeHtml(text) {
14342
14340
  }
14343
14341
 
14344
14342
  function renderSmartTable(data) {
14345
- console.log('renderSmartTable called with data:', data);
14346
-
14347
14343
  if (!data || data.length === 0) {
14348
- console.log('No data to render');
14349
14344
  return '<div class="p-8 text-center text-gray-400 text-sm font-mono">No data</div>';
14350
14345
  }
14351
14346
 
14352
14347
  const columns = Object.keys(data[0]);
14353
14348
  const maxRows = Math.min(data.length, 100);
14354
14349
 
14355
- console.log(\`Rendering table with \${columns.length} columns and \${maxRows} rows\`);
14356
-
14357
14350
  let html = \`
14358
14351
  <div class="overflow-x-auto scrollbar-thin">
14359
14352
  <table class="w-full text-xs font-mono">
@@ -14406,7 +14399,6 @@ function renderSmartTable(data) {
14406
14399
  html += \`<div class="px-3 py-2 text-xs text-slate-500 bg-slate-50 border-t border-slate-200 font-mono">Showing \${maxRows} of \${data.length} rows</div>\`;
14407
14400
  }
14408
14401
 
14409
- console.log('Table HTML generated successfully');
14410
14402
  return html;
14411
14403
  }
14412
14404
 
@@ -14414,7 +14406,6 @@ function executeQuery(uniqueId) {
14414
14406
  const operation = document.getElementById(\`query-operation-\${uniqueId}\`).value;
14415
14407
  const resultsDiv = document.getElementById(\`query-results-\${uniqueId}\`);
14416
14408
 
14417
- // Better loading feedback
14418
14409
  resultsDiv.innerHTML = \`
14419
14410
  <div class="p-8 text-center">
14420
14411
  <div class="inline-flex items-center gap-3 text-slate-600 font-mono text-sm">
@@ -14481,23 +14472,16 @@ function executeQuery(uniqueId) {
14481
14472
  }
14482
14473
 
14483
14474
  query.then(({ data, error }) => {
14484
- console.log('Query result:', { data, error });
14485
-
14486
14475
  if (error) {
14487
- console.error('Query error:', error);
14488
14476
  resultsDiv.innerHTML = \`<div class="p-4 text-red-600 text-sm font-mono bg-red-50 border border-red-200 rounded">Error: \${error.message}</div>\`;
14489
14477
  } else {
14490
- console.log('Query successful, data length:', data ? data.length : 0);
14491
14478
  if (data && data.length > 0) {
14492
- console.log('Calling renderSmartTable with data:', data);
14493
14479
  resultsDiv.innerHTML = renderSmartTable(data);
14494
14480
  } else {
14495
- console.log('No data returned');
14496
14481
  resultsDiv.innerHTML = '<div class="p-8 text-center text-gray-400 text-sm font-mono">No data returned</div>';
14497
14482
  }
14498
14483
  }
14499
14484
  }).catch((err) => {
14500
- console.error('Query execution error:', err);
14501
14485
  resultsDiv.innerHTML = \`<div class="p-4 text-red-600 text-sm font-mono bg-red-50 border border-red-200 rounded">Execution error: \${err.message}</div>\`;
14502
14486
  });
14503
14487
 
@@ -14509,7 +14493,6 @@ function executeQuery(uniqueId) {
14509
14493
  function executeRPC(rpcName, uniqueId, schema) {
14510
14494
  const resultsDiv = document.getElementById(\`rpc-results-\${uniqueId}\`);
14511
14495
 
14512
- // Loading feedback
14513
14496
  resultsDiv.innerHTML = \`
14514
14497
  <div class="p-8 text-center">
14515
14498
  <div class="inline-flex items-center gap-3 text-slate-600 font-mono text-sm">
@@ -14520,7 +14503,6 @@ function executeQuery(uniqueId) {
14520
14503
  \`;
14521
14504
 
14522
14505
  try {
14523
- // Collect parameters from form inputs
14524
14506
  const params = {};
14525
14507
  const paramInputs = document.querySelectorAll(\`[id^="rpc-param-"][id$="-\${uniqueId}"]\`);
14526
14508
 
@@ -14529,41 +14511,29 @@ function executeQuery(uniqueId) {
14529
14511
  const value = input.value.trim();
14530
14512
 
14531
14513
  if (value !== '') {
14532
- // Try to parse as JSON for complex types, otherwise use as string
14533
14514
  try {
14534
14515
  params[paramName] = JSON.parse(value);
14535
14516
  } catch {
14536
- // If JSON parsing fails, use the raw value
14537
14517
  params[paramName] = value;
14538
14518
  }
14539
14519
  }
14540
14520
  });
14541
14521
 
14542
- console.log('Executing RPC:', rpcName, 'in schema:', schema, 'with params:', params);
14543
-
14544
14522
  const cleanRpcName = rpcName.startsWith('rpc/') ? rpcName.slice(4) : rpcName;
14545
- console.log('Clean RPC name:', cleanRpcName);
14546
-
14547
14523
  const rpcCall = supabase.schema(schema).rpc(cleanRpcName, params);
14548
14524
 
14549
14525
  rpcCall.then(({ data, error }) => {
14550
- console.log('RPC result:', { data, error });
14551
-
14552
14526
  if (error) {
14553
- console.error('RPC error:', error);
14554
14527
  resultsDiv.innerHTML = \`<div class="p-4 text-red-600 text-sm font-mono bg-red-50 border border-red-200 rounded">Error: \${error.message}</div>\`;
14555
14528
  } else {
14556
- console.log('RPC successful, data:', data);
14557
14529
  if (data !== null && data !== undefined) {
14558
14530
  if (Array.isArray(data)) {
14559
- // If it's an array, render as table
14560
14531
  if (data.length > 0) {
14561
14532
  resultsDiv.innerHTML = renderSmartTable(data);
14562
14533
  } else {
14563
14534
  resultsDiv.innerHTML = '<div class="p-8 text-center text-gray-400 text-sm font-mono">RPC returned empty array</div>';
14564
14535
  }
14565
14536
  } else {
14566
- // If it's a single value, display it nicely
14567
14537
  resultsDiv.innerHTML = \`
14568
14538
  <div class="p-6">
14569
14539
  <div class="bg-slate-50 border border-slate-200 rounded-lg p-4">
@@ -14578,7 +14548,6 @@ function executeQuery(uniqueId) {
14578
14548
  }
14579
14549
  }
14580
14550
  }).catch((err) => {
14581
- console.error('RPC execution error:', err);
14582
14551
  resultsDiv.innerHTML = \`<div class="p-4 text-red-600 text-sm font-mono bg-red-50 border border-red-200 rounded">Execution error: \${err.message}</div>\`;
14583
14552
  });
14584
14553
 
@@ -14586,16 +14555,12 @@ function executeQuery(uniqueId) {
14586
14555
  resultsDiv.innerHTML = \`<div class="text-red-600">Error: \${err.message}</div>\`;
14587
14556
  }
14588
14557
  }
14589
-
14590
- // Show/hide query type sections
14558
+
14591
14559
  document.addEventListener('DOMContentLoaded', function() {
14592
- // Add event listeners to all operation selects
14593
14560
  document.querySelectorAll('[id^="query-operation-"]').forEach(select => {
14594
14561
  select.addEventListener('change', function() {
14595
14562
  const uniqueId = this.id.replace('query-operation-', '');
14596
- // Hide all query types for this specific interface
14597
14563
  document.querySelectorAll(\`[id$="-\${uniqueId}"].query-type\`).forEach(el => el.classList.add('hidden'));
14598
- // Show the selected query type
14599
14564
  const targetId = this.value + '-query-' + uniqueId;
14600
14565
  const target = document.getElementById(targetId);
14601
14566
  if (target) target.classList.remove('hidden');
@@ -14738,10 +14703,13 @@ function APICredentialsDisplay({ url, key }) {
14738
14703
  class: "flex items-center gap-1",
14739
14704
  children: [
14740
14705
  /* @__PURE__ */ $jsxDEV("span", {
14706
+ class: "flex-1 min-w-0",
14741
14707
  children: [
14742
- "Key: ",
14708
+ "Key:",
14709
+ " ",
14743
14710
  /* @__PURE__ */ $jsxDEV("span", {
14744
14711
  id: "api-key-display",
14712
+ class: "break-all",
14745
14713
  children: [
14746
14714
  key.substring(0, 20),
14747
14715
  "..."
@@ -14751,7 +14719,7 @@ function APICredentialsDisplay({ url, key }) {
14751
14719
  }, undefined, true, undefined, this),
14752
14720
  /* @__PURE__ */ $jsxDEV("button", {
14753
14721
  id: "api-key-toggle",
14754
- class: "px-1 py-0.5 text-xs bg-blue-100 text-blue-800 rounded hover:bg-blue-200 transition-colors",
14722
+ class: "px-1 py-0.5 text-xs bg-blue-100 text-blue-800 rounded hover:bg-blue-200 transition-colors whitespace-nowrap flex-shrink-0",
14755
14723
  onclick: "toggleApiKey()",
14756
14724
  children: "Show Full Key"
14757
14725
  }, undefined, false, undefined, this)
@@ -14881,12 +14849,12 @@ function TableRow({
14881
14849
  switch (access?.status) {
14882
14850
  case "readable":
14883
14851
  statusClass = "bg-green-100 text-green-800 border-green-200";
14884
- statusText = "Data exposed";
14852
+ statusText = `~${access.rowCount ?? "?"} rows exposed`;
14885
14853
  statusIcon = "[+]";
14886
14854
  break;
14887
14855
  case "empty":
14888
14856
  statusClass = "bg-yellow-100 text-yellow-800 border-yellow-200";
14889
- statusText = "Empty or RLS protected";
14857
+ statusText = "0 rows - empty or RLS";
14890
14858
  statusIcon = "[-]";
14891
14859
  break;
14892
14860
  case "denied":
@@ -15436,8 +15404,11 @@ class HtmlRendererService {
15436
15404
  content: "width=device-width, initial-scale=1.0"
15437
15405
  }, undefined, false, undefined, this),
15438
15406
  /* @__PURE__ */ $jsxDEV("title", {
15439
- children: "Supabase Database Analysis Report"
15440
- }, undefined, false, undefined, this),
15407
+ children: [
15408
+ result.summary.domain,
15409
+ " - Security Analysis"
15410
+ ]
15411
+ }, undefined, true, undefined, this),
15441
15412
  /* @__PURE__ */ $jsxDEV("script", {
15442
15413
  src: "https://cdn.tailwindcss.com"
15443
15414
  }, undefined, false, undefined, this),
@@ -15541,7 +15512,7 @@ class HtmlRendererService {
15541
15512
  children: [
15542
15513
  /* @__PURE__ */ $jsxDEV("h1", {
15543
15514
  class: "text-2xl font-bold text-slate-900 mb-1 font-mono",
15544
- children: "Supabase Database Analysis"
15515
+ children: result.summary.domain
15545
15516
  }, undefined, false, undefined, this),
15546
15517
  /* @__PURE__ */ $jsxDEV("p", {
15547
15518
  class: "text-slate-600 font-mono text-sm",
@@ -15624,8 +15595,20 @@ class HtmlRendererService {
15624
15595
  /* @__PURE__ */ $jsxDEV("footer", {
15625
15596
  class: "mt-12 text-center text-slate-500 text-sm font-mono",
15626
15597
  children: /* @__PURE__ */ $jsxDEV("p", {
15627
- children: "Generated by supascan - Security analysis tool for Supabase"
15628
- }, undefined, false, undefined, this)
15598
+ children: [
15599
+ "Generated by",
15600
+ " ",
15601
+ /* @__PURE__ */ $jsxDEV("a", {
15602
+ href: "https://github.com/abhishekg999/supascan",
15603
+ target: "_blank",
15604
+ rel: "noopener noreferrer",
15605
+ class: "text-supabase-green hover:text-emerald-600 transition-colors underline",
15606
+ children: "supascan"
15607
+ }, undefined, false, undefined, this),
15608
+ " ",
15609
+ "- Security analysis tool for Supabase"
15610
+ ]
15611
+ }, undefined, true, undefined, this)
15629
15612
  }, undefined, false, undefined, this)
15630
15613
  ]
15631
15614
  }, undefined, true, undefined, this)
@@ -15719,7 +15702,7 @@ function displayAnalysisResult(result) {
15719
15702
  switch (access?.status) {
15720
15703
  case "readable":
15721
15704
  indicator = import_picocolors2.default.green("[+]");
15722
- description = import_picocolors2.default.dim("(data exposed)");
15705
+ description = import_picocolors2.default.dim(`(~${access.rowCount ?? "?"} rows exposed)`);
15723
15706
  break;
15724
15707
  case "empty":
15725
15708
  indicator = import_picocolors2.default.yellow("[-]");
@@ -16184,7 +16167,7 @@ var experimentalWarning2 = onlyOnce2(() => {
16184
16167
  }
16185
16168
  });
16186
16169
  // package.json
16187
- var version = "0.0.9";
16170
+ var version = "0.1.0";
16188
16171
 
16189
16172
  // version.ts
16190
16173
  var VERSION = version;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "supascan",
3
- "version": "0.0.9",
4
- "description": "Security analysis tool for Supabase databases",
3
+ "version": "0.1.0",
4
+ "description": "Automated security scanner for Supabase databases - detect exposed data, analyze RLS policies, and test RPC functions",
5
5
  "license": "MIT",
6
6
  "author": "Abhishek Govindarasu",
7
7
  "main": "dist/supascan.js",