supascan 0.0.10 → 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.
- package/README.md +60 -110
- package/dist/supascan.js +49 -77
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,145 +1,88 @@
|
|
|
1
1
|
# supascan
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/abhishekg999/supascan/actions/workflows/tests.yml) [](https://raw.githubusercontent.com/abhishekg999/supascan/master/LICENCE)
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
|
|
8
|
-
bun install -g supascan
|
|
9
|
-
```
|
|
7
|
+
## Features
|
|
10
8
|
|
|
11
|
-
|
|
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
|
-
|
|
18
|
+
## Installation
|
|
14
19
|
|
|
15
|
-
|
|
20
|
+
**NPM:**
|
|
16
21
|
|
|
17
22
|
```bash
|
|
18
|
-
|
|
23
|
+
npm install -g supascan
|
|
19
24
|
```
|
|
20
25
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
#### Database Analysis
|
|
26
|
+
**Bun:**
|
|
24
27
|
|
|
25
28
|
```bash
|
|
26
|
-
|
|
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
|
-
|
|
32
|
+
**From source:**
|
|
40
33
|
|
|
41
34
|
```bash
|
|
42
|
-
|
|
43
|
-
supascan
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
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
|
-
|
|
43
|
+
To get basic options and usage:
|
|
63
44
|
|
|
64
45
|
```bash
|
|
65
|
-
|
|
66
|
-
supascan --extract https://example.com/app.js --url <url> --key <key>
|
|
46
|
+
supascan --help
|
|
67
47
|
```
|
|
68
48
|
|
|
69
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
55
|
+
# Generate HTML report
|
|
56
|
+
supascan --url https://your-project.supabase.co --key your-anon-key --html
|
|
117
57
|
|
|
118
|
-
|
|
119
|
-
supascan --url https://
|
|
120
|
-
```
|
|
58
|
+
# Analyze specific schema
|
|
59
|
+
supascan --url https://your-project.supabase.co --key your-anon-key --schema public
|
|
121
60
|
|
|
122
|
-
|
|
61
|
+
# Dump table data
|
|
62
|
+
supascan --url https://your-project.supabase.co --key your-anon-key --dump public.users --limit 100
|
|
123
63
|
|
|
124
|
-
|
|
125
|
-
supascan --url https://
|
|
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
|
-
|
|
68
|
+
## What supascan Detects
|
|
129
69
|
|
|
130
|
-
|
|
131
|
-
|
|
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
|
|
78
|
+
⚠️ **Important**: This tool is for authorized security testing only.
|
|
137
79
|
|
|
138
|
-
-
|
|
139
|
-
-
|
|
140
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
157
|
-
bun test
|
|
103
|
+
## License
|
|
158
104
|
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
13935
|
-
|
|
13936
|
-
|
|
13937
|
-
|
|
13938
|
-
|
|
13939
|
-
|
|
13940
|
-
|
|
13941
|
-
|
|
13942
|
-
|
|
13943
|
-
|
|
13944
|
-
|
|
13945
|
-
|
|
13946
|
-
|
|
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
|
};
|
|
@@ -14266,15 +14271,11 @@ function toggleApiKey() {
|
|
|
14266
14271
|
|
|
14267
14272
|
async function saveReport() {
|
|
14268
14273
|
try {
|
|
14269
|
-
// Generate filename with timestamp and domain
|
|
14270
14274
|
const domain = '${domain}';
|
|
14271
|
-
const timestamp = new Date().toISOString().split('T')[0];
|
|
14275
|
+
const timestamp = new Date().toISOString().split('T')[0];
|
|
14272
14276
|
const filename = \`supabase-analysis-\${domain}-\${timestamp}.html\`;
|
|
14273
|
-
|
|
14274
|
-
// Get the current HTML content
|
|
14275
14277
|
const htmlContent = document.documentElement.outerHTML;
|
|
14276
14278
|
|
|
14277
|
-
// Check if File System Access API is supported
|
|
14278
14279
|
if ('showSaveFilePicker' in window) {
|
|
14279
14280
|
const fileHandle = await window.showSaveFilePicker({
|
|
14280
14281
|
suggestedName: filename,
|
|
@@ -14289,11 +14290,8 @@ async function saveReport() {
|
|
|
14289
14290
|
const writable = await fileHandle.createWritable();
|
|
14290
14291
|
await writable.write(htmlContent);
|
|
14291
14292
|
await writable.close();
|
|
14292
|
-
|
|
14293
|
-
// Show success message
|
|
14294
14293
|
showNotification('Report saved successfully!', 'success');
|
|
14295
14294
|
} else {
|
|
14296
|
-
// Fallback for browsers that don't support File System Access API
|
|
14297
14295
|
const blob = new Blob([htmlContent], { type: 'text/html' });
|
|
14298
14296
|
const url = URL.createObjectURL(blob);
|
|
14299
14297
|
const a = document.createElement('a');
|
|
@@ -14307,17 +14305,13 @@ async function saveReport() {
|
|
|
14307
14305
|
showNotification('Report downloaded successfully!', 'success');
|
|
14308
14306
|
}
|
|
14309
14307
|
} catch (error) {
|
|
14310
|
-
if (error.name === 'AbortError')
|
|
14311
|
-
// User cancelled the save dialog
|
|
14312
|
-
return;
|
|
14313
|
-
}
|
|
14308
|
+
if (error.name === 'AbortError') return;
|
|
14314
14309
|
console.error('Error saving report:', error);
|
|
14315
14310
|
showNotification('Failed to save report: ' + error.message, 'error');
|
|
14316
14311
|
}
|
|
14317
14312
|
}
|
|
14318
14313
|
|
|
14319
14314
|
function showNotification(message, type = 'info') {
|
|
14320
|
-
// Create notification element
|
|
14321
14315
|
const notification = document.createElement('div');
|
|
14322
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\`;
|
|
14323
14317
|
|
|
@@ -14332,17 +14326,10 @@ function showNotification(message, type = 'info') {
|
|
|
14332
14326
|
notification.textContent = message;
|
|
14333
14327
|
document.body.appendChild(notification);
|
|
14334
14328
|
|
|
14335
|
-
|
|
14336
|
-
setTimeout(() => {
|
|
14337
|
-
notification.classList.remove('translate-x-full');
|
|
14338
|
-
}, 100);
|
|
14339
|
-
|
|
14340
|
-
// Remove after 3 seconds
|
|
14329
|
+
setTimeout(() => notification.classList.remove('translate-x-full'), 100);
|
|
14341
14330
|
setTimeout(() => {
|
|
14342
14331
|
notification.classList.add('translate-x-full');
|
|
14343
|
-
setTimeout(() =>
|
|
14344
|
-
document.body.removeChild(notification);
|
|
14345
|
-
}, 300);
|
|
14332
|
+
setTimeout(() => document.body.removeChild(notification), 300);
|
|
14346
14333
|
}, 3000);
|
|
14347
14334
|
}
|
|
14348
14335
|
|
|
@@ -14353,18 +14340,13 @@ function escapeHtml(text) {
|
|
|
14353
14340
|
}
|
|
14354
14341
|
|
|
14355
14342
|
function renderSmartTable(data) {
|
|
14356
|
-
console.log('renderSmartTable called with data:', data);
|
|
14357
|
-
|
|
14358
14343
|
if (!data || data.length === 0) {
|
|
14359
|
-
console.log('No data to render');
|
|
14360
14344
|
return '<div class="p-8 text-center text-gray-400 text-sm font-mono">No data</div>';
|
|
14361
14345
|
}
|
|
14362
14346
|
|
|
14363
14347
|
const columns = Object.keys(data[0]);
|
|
14364
14348
|
const maxRows = Math.min(data.length, 100);
|
|
14365
14349
|
|
|
14366
|
-
console.log(\`Rendering table with \${columns.length} columns and \${maxRows} rows\`);
|
|
14367
|
-
|
|
14368
14350
|
let html = \`
|
|
14369
14351
|
<div class="overflow-x-auto scrollbar-thin">
|
|
14370
14352
|
<table class="w-full text-xs font-mono">
|
|
@@ -14417,7 +14399,6 @@ function renderSmartTable(data) {
|
|
|
14417
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>\`;
|
|
14418
14400
|
}
|
|
14419
14401
|
|
|
14420
|
-
console.log('Table HTML generated successfully');
|
|
14421
14402
|
return html;
|
|
14422
14403
|
}
|
|
14423
14404
|
|
|
@@ -14425,7 +14406,6 @@ function executeQuery(uniqueId) {
|
|
|
14425
14406
|
const operation = document.getElementById(\`query-operation-\${uniqueId}\`).value;
|
|
14426
14407
|
const resultsDiv = document.getElementById(\`query-results-\${uniqueId}\`);
|
|
14427
14408
|
|
|
14428
|
-
// Better loading feedback
|
|
14429
14409
|
resultsDiv.innerHTML = \`
|
|
14430
14410
|
<div class="p-8 text-center">
|
|
14431
14411
|
<div class="inline-flex items-center gap-3 text-slate-600 font-mono text-sm">
|
|
@@ -14492,23 +14472,16 @@ function executeQuery(uniqueId) {
|
|
|
14492
14472
|
}
|
|
14493
14473
|
|
|
14494
14474
|
query.then(({ data, error }) => {
|
|
14495
|
-
console.log('Query result:', { data, error });
|
|
14496
|
-
|
|
14497
14475
|
if (error) {
|
|
14498
|
-
console.error('Query error:', error);
|
|
14499
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>\`;
|
|
14500
14477
|
} else {
|
|
14501
|
-
console.log('Query successful, data length:', data ? data.length : 0);
|
|
14502
14478
|
if (data && data.length > 0) {
|
|
14503
|
-
console.log('Calling renderSmartTable with data:', data);
|
|
14504
14479
|
resultsDiv.innerHTML = renderSmartTable(data);
|
|
14505
14480
|
} else {
|
|
14506
|
-
console.log('No data returned');
|
|
14507
14481
|
resultsDiv.innerHTML = '<div class="p-8 text-center text-gray-400 text-sm font-mono">No data returned</div>';
|
|
14508
14482
|
}
|
|
14509
14483
|
}
|
|
14510
14484
|
}).catch((err) => {
|
|
14511
|
-
console.error('Query execution error:', err);
|
|
14512
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>\`;
|
|
14513
14486
|
});
|
|
14514
14487
|
|
|
@@ -14520,7 +14493,6 @@ function executeQuery(uniqueId) {
|
|
|
14520
14493
|
function executeRPC(rpcName, uniqueId, schema) {
|
|
14521
14494
|
const resultsDiv = document.getElementById(\`rpc-results-\${uniqueId}\`);
|
|
14522
14495
|
|
|
14523
|
-
// Loading feedback
|
|
14524
14496
|
resultsDiv.innerHTML = \`
|
|
14525
14497
|
<div class="p-8 text-center">
|
|
14526
14498
|
<div class="inline-flex items-center gap-3 text-slate-600 font-mono text-sm">
|
|
@@ -14531,7 +14503,6 @@ function executeQuery(uniqueId) {
|
|
|
14531
14503
|
\`;
|
|
14532
14504
|
|
|
14533
14505
|
try {
|
|
14534
|
-
// Collect parameters from form inputs
|
|
14535
14506
|
const params = {};
|
|
14536
14507
|
const paramInputs = document.querySelectorAll(\`[id^="rpc-param-"][id$="-\${uniqueId}"]\`);
|
|
14537
14508
|
|
|
@@ -14540,41 +14511,29 @@ function executeQuery(uniqueId) {
|
|
|
14540
14511
|
const value = input.value.trim();
|
|
14541
14512
|
|
|
14542
14513
|
if (value !== '') {
|
|
14543
|
-
// Try to parse as JSON for complex types, otherwise use as string
|
|
14544
14514
|
try {
|
|
14545
14515
|
params[paramName] = JSON.parse(value);
|
|
14546
14516
|
} catch {
|
|
14547
|
-
// If JSON parsing fails, use the raw value
|
|
14548
14517
|
params[paramName] = value;
|
|
14549
14518
|
}
|
|
14550
14519
|
}
|
|
14551
14520
|
});
|
|
14552
14521
|
|
|
14553
|
-
console.log('Executing RPC:', rpcName, 'in schema:', schema, 'with params:', params);
|
|
14554
|
-
|
|
14555
14522
|
const cleanRpcName = rpcName.startsWith('rpc/') ? rpcName.slice(4) : rpcName;
|
|
14556
|
-
console.log('Clean RPC name:', cleanRpcName);
|
|
14557
|
-
|
|
14558
14523
|
const rpcCall = supabase.schema(schema).rpc(cleanRpcName, params);
|
|
14559
14524
|
|
|
14560
14525
|
rpcCall.then(({ data, error }) => {
|
|
14561
|
-
console.log('RPC result:', { data, error });
|
|
14562
|
-
|
|
14563
14526
|
if (error) {
|
|
14564
|
-
console.error('RPC error:', error);
|
|
14565
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>\`;
|
|
14566
14528
|
} else {
|
|
14567
|
-
console.log('RPC successful, data:', data);
|
|
14568
14529
|
if (data !== null && data !== undefined) {
|
|
14569
14530
|
if (Array.isArray(data)) {
|
|
14570
|
-
// If it's an array, render as table
|
|
14571
14531
|
if (data.length > 0) {
|
|
14572
14532
|
resultsDiv.innerHTML = renderSmartTable(data);
|
|
14573
14533
|
} else {
|
|
14574
14534
|
resultsDiv.innerHTML = '<div class="p-8 text-center text-gray-400 text-sm font-mono">RPC returned empty array</div>';
|
|
14575
14535
|
}
|
|
14576
14536
|
} else {
|
|
14577
|
-
// If it's a single value, display it nicely
|
|
14578
14537
|
resultsDiv.innerHTML = \`
|
|
14579
14538
|
<div class="p-6">
|
|
14580
14539
|
<div class="bg-slate-50 border border-slate-200 rounded-lg p-4">
|
|
@@ -14589,7 +14548,6 @@ function executeQuery(uniqueId) {
|
|
|
14589
14548
|
}
|
|
14590
14549
|
}
|
|
14591
14550
|
}).catch((err) => {
|
|
14592
|
-
console.error('RPC execution error:', err);
|
|
14593
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>\`;
|
|
14594
14552
|
});
|
|
14595
14553
|
|
|
@@ -14597,16 +14555,12 @@ function executeQuery(uniqueId) {
|
|
|
14597
14555
|
resultsDiv.innerHTML = \`<div class="text-red-600">Error: \${err.message}</div>\`;
|
|
14598
14556
|
}
|
|
14599
14557
|
}
|
|
14600
|
-
|
|
14601
|
-
// Show/hide query type sections
|
|
14558
|
+
|
|
14602
14559
|
document.addEventListener('DOMContentLoaded', function() {
|
|
14603
|
-
// Add event listeners to all operation selects
|
|
14604
14560
|
document.querySelectorAll('[id^="query-operation-"]').forEach(select => {
|
|
14605
14561
|
select.addEventListener('change', function() {
|
|
14606
14562
|
const uniqueId = this.id.replace('query-operation-', '');
|
|
14607
|
-
// Hide all query types for this specific interface
|
|
14608
14563
|
document.querySelectorAll(\`[id$="-\${uniqueId}"].query-type\`).forEach(el => el.classList.add('hidden'));
|
|
14609
|
-
// Show the selected query type
|
|
14610
14564
|
const targetId = this.value + '-query-' + uniqueId;
|
|
14611
14565
|
const target = document.getElementById(targetId);
|
|
14612
14566
|
if (target) target.classList.remove('hidden');
|
|
@@ -14749,10 +14703,13 @@ function APICredentialsDisplay({ url, key }) {
|
|
|
14749
14703
|
class: "flex items-center gap-1",
|
|
14750
14704
|
children: [
|
|
14751
14705
|
/* @__PURE__ */ $jsxDEV("span", {
|
|
14706
|
+
class: "flex-1 min-w-0",
|
|
14752
14707
|
children: [
|
|
14753
|
-
"Key:
|
|
14708
|
+
"Key:",
|
|
14709
|
+
" ",
|
|
14754
14710
|
/* @__PURE__ */ $jsxDEV("span", {
|
|
14755
14711
|
id: "api-key-display",
|
|
14712
|
+
class: "break-all",
|
|
14756
14713
|
children: [
|
|
14757
14714
|
key.substring(0, 20),
|
|
14758
14715
|
"..."
|
|
@@ -14762,7 +14719,7 @@ function APICredentialsDisplay({ url, key }) {
|
|
|
14762
14719
|
}, undefined, true, undefined, this),
|
|
14763
14720
|
/* @__PURE__ */ $jsxDEV("button", {
|
|
14764
14721
|
id: "api-key-toggle",
|
|
14765
|
-
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",
|
|
14766
14723
|
onclick: "toggleApiKey()",
|
|
14767
14724
|
children: "Show Full Key"
|
|
14768
14725
|
}, undefined, false, undefined, this)
|
|
@@ -15447,8 +15404,11 @@ class HtmlRendererService {
|
|
|
15447
15404
|
content: "width=device-width, initial-scale=1.0"
|
|
15448
15405
|
}, undefined, false, undefined, this),
|
|
15449
15406
|
/* @__PURE__ */ $jsxDEV("title", {
|
|
15450
|
-
children:
|
|
15451
|
-
|
|
15407
|
+
children: [
|
|
15408
|
+
result.summary.domain,
|
|
15409
|
+
" - Security Analysis"
|
|
15410
|
+
]
|
|
15411
|
+
}, undefined, true, undefined, this),
|
|
15452
15412
|
/* @__PURE__ */ $jsxDEV("script", {
|
|
15453
15413
|
src: "https://cdn.tailwindcss.com"
|
|
15454
15414
|
}, undefined, false, undefined, this),
|
|
@@ -15552,7 +15512,7 @@ class HtmlRendererService {
|
|
|
15552
15512
|
children: [
|
|
15553
15513
|
/* @__PURE__ */ $jsxDEV("h1", {
|
|
15554
15514
|
class: "text-2xl font-bold text-slate-900 mb-1 font-mono",
|
|
15555
|
-
children:
|
|
15515
|
+
children: result.summary.domain
|
|
15556
15516
|
}, undefined, false, undefined, this),
|
|
15557
15517
|
/* @__PURE__ */ $jsxDEV("p", {
|
|
15558
15518
|
class: "text-slate-600 font-mono text-sm",
|
|
@@ -15635,8 +15595,20 @@ class HtmlRendererService {
|
|
|
15635
15595
|
/* @__PURE__ */ $jsxDEV("footer", {
|
|
15636
15596
|
class: "mt-12 text-center text-slate-500 text-sm font-mono",
|
|
15637
15597
|
children: /* @__PURE__ */ $jsxDEV("p", {
|
|
15638
|
-
children:
|
|
15639
|
-
|
|
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)
|
|
15640
15612
|
}, undefined, false, undefined, this)
|
|
15641
15613
|
]
|
|
15642
15614
|
}, undefined, true, undefined, this)
|
|
@@ -16195,7 +16167,7 @@ var experimentalWarning2 = onlyOnce2(() => {
|
|
|
16195
16167
|
}
|
|
16196
16168
|
});
|
|
16197
16169
|
// package.json
|
|
16198
|
-
var version = "0.0
|
|
16170
|
+
var version = "0.1.0";
|
|
16199
16171
|
|
|
16200
16172
|
// version.ts
|
|
16201
16173
|
var VERSION = version;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "supascan",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "
|
|
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",
|