ship-safe 6.1.1 → 6.2.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 +735 -641
- package/cli/agents/api-fuzzer.js +345 -345
- package/cli/agents/auth-bypass-agent.js +348 -348
- package/cli/agents/base-agent.js +272 -272
- package/cli/agents/cicd-scanner.js +236 -201
- package/cli/agents/config-auditor.js +521 -521
- package/cli/agents/deep-analyzer.js +6 -2
- package/cli/agents/git-history-scanner.js +170 -170
- package/cli/agents/html-reporter.js +568 -568
- package/cli/agents/index.js +84 -84
- package/cli/agents/injection-tester.js +500 -500
- package/cli/agents/llm-redteam.js +251 -251
- package/cli/agents/mobile-scanner.js +231 -231
- package/cli/agents/orchestrator.js +322 -322
- package/cli/agents/pii-compliance-agent.js +301 -301
- package/cli/agents/scoring-engine.js +248 -248
- package/cli/agents/supabase-rls-agent.js +154 -154
- package/cli/agents/supply-chain-agent.js +650 -507
- package/cli/bin/ship-safe.js +452 -426
- package/cli/commands/agent.js +608 -608
- package/cli/commands/audit.js +986 -980
- package/cli/commands/baseline.js +193 -193
- package/cli/commands/ci.js +342 -342
- package/cli/commands/deps.js +516 -516
- package/cli/commands/doctor.js +159 -159
- package/cli/commands/fix.js +218 -218
- package/cli/commands/hooks.js +268 -0
- package/cli/commands/init.js +407 -407
- package/cli/commands/mcp.js +304 -304
- package/cli/commands/red-team.js +7 -1
- package/cli/commands/remediate.js +798 -798
- package/cli/commands/rotate.js +571 -571
- package/cli/commands/scan.js +569 -569
- package/cli/commands/score.js +449 -449
- package/cli/commands/watch.js +281 -281
- package/cli/hooks/patterns.js +313 -0
- package/cli/hooks/post-tool-use.js +140 -0
- package/cli/hooks/pre-tool-use.js +186 -0
- package/cli/index.js +73 -69
- package/cli/providers/llm-provider.js +397 -287
- package/cli/utils/autofix-rules.js +74 -74
- package/cli/utils/cache-manager.js +311 -311
- package/cli/utils/output.js +230 -230
- package/cli/utils/patterns.js +1121 -1121
- package/cli/utils/pdf-generator.js +94 -94
- package/package.json +69 -69
- package/configs/supabase/rls-templates.sql +0 -242
|
@@ -1,94 +1,94 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PDF Generator
|
|
3
|
-
* ==============
|
|
4
|
-
*
|
|
5
|
-
* Zero-dependency PDF generation via Chrome/Chromium headless mode.
|
|
6
|
-
* Falls back to generating a print-optimized HTML file if Chrome is not found.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import fs from 'fs';
|
|
10
|
-
import path from 'path';
|
|
11
|
-
import { execFileSync } from 'child_process';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Well-known Chrome/Chromium paths by platform.
|
|
15
|
-
*/
|
|
16
|
-
function findChrome() {
|
|
17
|
-
const candidates = process.platform === 'win32'
|
|
18
|
-
? [
|
|
19
|
-
process.env.CHROME_PATH,
|
|
20
|
-
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
|
21
|
-
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
|
|
22
|
-
process.env.LOCALAPPDATA && path.join(process.env.LOCALAPPDATA, 'Google\\Chrome\\Application\\chrome.exe'),
|
|
23
|
-
]
|
|
24
|
-
: process.platform === 'darwin'
|
|
25
|
-
? [
|
|
26
|
-
process.env.CHROME_PATH,
|
|
27
|
-
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
28
|
-
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
29
|
-
]
|
|
30
|
-
: [
|
|
31
|
-
process.env.CHROME_PATH,
|
|
32
|
-
'/usr/bin/google-chrome',
|
|
33
|
-
'/usr/bin/google-chrome-stable',
|
|
34
|
-
'/usr/bin/chromium',
|
|
35
|
-
'/usr/bin/chromium-browser',
|
|
36
|
-
'/snap/bin/chromium',
|
|
37
|
-
];
|
|
38
|
-
|
|
39
|
-
for (const c of candidates) {
|
|
40
|
-
if (c && fs.existsSync(c)) return c;
|
|
41
|
-
}
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Check if Chrome is available.
|
|
47
|
-
*/
|
|
48
|
-
export function isChromeAvailable() {
|
|
49
|
-
return findChrome() !== null;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Generate PDF from an HTML file using Chrome headless.
|
|
54
|
-
* Returns the output path, or null if Chrome is not available.
|
|
55
|
-
*/
|
|
56
|
-
export function generatePDF(htmlPath, outputPath) {
|
|
57
|
-
const chrome = findChrome();
|
|
58
|
-
if (!chrome) return null;
|
|
59
|
-
|
|
60
|
-
try {
|
|
61
|
-
const args = [
|
|
62
|
-
'--headless',
|
|
63
|
-
'--disable-gpu',
|
|
64
|
-
'--no-sandbox',
|
|
65
|
-
`--print-to-pdf=${outputPath}`,
|
|
66
|
-
'--print-to-pdf-no-header',
|
|
67
|
-
htmlPath,
|
|
68
|
-
];
|
|
69
|
-
execFileSync(chrome, args, { timeout: 30000, stdio: 'pipe' }); // ship-safe-ignore — execFileSync with fixed chrome binary path; no user input in command
|
|
70
|
-
return outputPath;
|
|
71
|
-
} catch {
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Generate a print-optimized HTML file as PDF fallback.
|
|
78
|
-
*/
|
|
79
|
-
export function generatePrintHTML(htmlPath, outputPath) {
|
|
80
|
-
let html = fs.readFileSync(htmlPath, 'utf-8');
|
|
81
|
-
// Add print-optimized styles
|
|
82
|
-
const printCSS = `
|
|
83
|
-
<style media="print">
|
|
84
|
-
body { background: #fff !important; color: #1e293b !important; }
|
|
85
|
-
.score-card, .stat, .summary-card, .toc { background: #f8fafc !important; border: 1px solid #e2e8f0 !important; }
|
|
86
|
-
table, th, td { border: 1px solid #e2e8f0 !important; }
|
|
87
|
-
code { background: #f1f5f9 !important; color: #0f172a !important; }
|
|
88
|
-
pre { background: #f1f5f9 !important; }
|
|
89
|
-
a { color: #0369a1 !important; }
|
|
90
|
-
</style>`;
|
|
91
|
-
html = html.replace('</head>', printCSS + '\n</head>');
|
|
92
|
-
fs.writeFileSync(outputPath, html);
|
|
93
|
-
return outputPath;
|
|
94
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* PDF Generator
|
|
3
|
+
* ==============
|
|
4
|
+
*
|
|
5
|
+
* Zero-dependency PDF generation via Chrome/Chromium headless mode.
|
|
6
|
+
* Falls back to generating a print-optimized HTML file if Chrome is not found.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import { execFileSync } from 'child_process';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Well-known Chrome/Chromium paths by platform.
|
|
15
|
+
*/
|
|
16
|
+
function findChrome() {
|
|
17
|
+
const candidates = process.platform === 'win32'
|
|
18
|
+
? [
|
|
19
|
+
process.env.CHROME_PATH,
|
|
20
|
+
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
|
21
|
+
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
|
|
22
|
+
process.env.LOCALAPPDATA && path.join(process.env.LOCALAPPDATA, 'Google\\Chrome\\Application\\chrome.exe'),
|
|
23
|
+
]
|
|
24
|
+
: process.platform === 'darwin'
|
|
25
|
+
? [
|
|
26
|
+
process.env.CHROME_PATH,
|
|
27
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
28
|
+
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
29
|
+
]
|
|
30
|
+
: [
|
|
31
|
+
process.env.CHROME_PATH,
|
|
32
|
+
'/usr/bin/google-chrome',
|
|
33
|
+
'/usr/bin/google-chrome-stable',
|
|
34
|
+
'/usr/bin/chromium',
|
|
35
|
+
'/usr/bin/chromium-browser',
|
|
36
|
+
'/snap/bin/chromium',
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
for (const c of candidates) {
|
|
40
|
+
if (c && fs.existsSync(c)) return c;
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Check if Chrome is available.
|
|
47
|
+
*/
|
|
48
|
+
export function isChromeAvailable() {
|
|
49
|
+
return findChrome() !== null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Generate PDF from an HTML file using Chrome headless.
|
|
54
|
+
* Returns the output path, or null if Chrome is not available.
|
|
55
|
+
*/
|
|
56
|
+
export function generatePDF(htmlPath, outputPath) {
|
|
57
|
+
const chrome = findChrome();
|
|
58
|
+
if (!chrome) return null;
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const args = [
|
|
62
|
+
'--headless',
|
|
63
|
+
'--disable-gpu',
|
|
64
|
+
'--no-sandbox',
|
|
65
|
+
`--print-to-pdf=${outputPath}`,
|
|
66
|
+
'--print-to-pdf-no-header',
|
|
67
|
+
htmlPath,
|
|
68
|
+
];
|
|
69
|
+
execFileSync(chrome, args, { timeout: 30000, stdio: 'pipe' }); // ship-safe-ignore — execFileSync with fixed chrome binary path; no user input in command
|
|
70
|
+
return outputPath;
|
|
71
|
+
} catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Generate a print-optimized HTML file as PDF fallback.
|
|
78
|
+
*/
|
|
79
|
+
export function generatePrintHTML(htmlPath, outputPath) {
|
|
80
|
+
let html = fs.readFileSync(htmlPath, 'utf-8');
|
|
81
|
+
// Add print-optimized styles
|
|
82
|
+
const printCSS = `
|
|
83
|
+
<style media="print">
|
|
84
|
+
body { background: #fff !important; color: #1e293b !important; }
|
|
85
|
+
.score-card, .stat, .summary-card, .toc { background: #f8fafc !important; border: 1px solid #e2e8f0 !important; }
|
|
86
|
+
table, th, td { border: 1px solid #e2e8f0 !important; }
|
|
87
|
+
code { background: #f1f5f9 !important; color: #0f172a !important; }
|
|
88
|
+
pre { background: #f1f5f9 !important; }
|
|
89
|
+
a { color: #0369a1 !important; }
|
|
90
|
+
</style>`;
|
|
91
|
+
html = html.replace('</head>', printCSS + '\n</head>');
|
|
92
|
+
fs.writeFileSync(outputPath, html);
|
|
93
|
+
return outputPath;
|
|
94
|
+
}
|
package/package.json
CHANGED
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "ship-safe",
|
|
3
|
-
"version": "6.
|
|
4
|
-
"description": "AI-powered multi-agent security platform. 18 agents scan 80+ attack classes with LLM-powered deep analysis. Red team your code before attackers do.",
|
|
5
|
-
"main": "cli/index.js",
|
|
6
|
-
"bin": {
|
|
7
|
-
"ship-safe": "cli/bin/ship-safe.js"
|
|
8
|
-
},
|
|
9
|
-
"type": "module",
|
|
10
|
-
"scripts": {
|
|
11
|
-
"test": "node --test cli/__tests__/*.test.js",
|
|
12
|
-
"lint": "eslint cli/",
|
|
13
|
-
"ship-safe": "node cli/bin/ship-safe.js"
|
|
14
|
-
},
|
|
15
|
-
"keywords": [
|
|
16
|
-
"security",
|
|
17
|
-
"secrets",
|
|
18
|
-
"scanner",
|
|
19
|
-
"sast",
|
|
20
|
-
"devsecops",
|
|
21
|
-
"red-team",
|
|
22
|
-
"penetration-testing",
|
|
23
|
-
"vulnerability-scanner",
|
|
24
|
-
"sbom",
|
|
25
|
-
"owasp",
|
|
26
|
-
"sql-injection",
|
|
27
|
-
"xss",
|
|
28
|
-
"ssrf",
|
|
29
|
-
"supply-chain",
|
|
30
|
-
"llm-security",
|
|
31
|
-
"prompt-injection",
|
|
32
|
-
"api-security",
|
|
33
|
-
"docker-security",
|
|
34
|
-
"kubernetes",
|
|
35
|
-
"cicd-security",
|
|
36
|
-
"mobile-security",
|
|
37
|
-
"jwt",
|
|
38
|
-
"cors",
|
|
39
|
-
"cli"
|
|
40
|
-
],
|
|
41
|
-
"author": "ship-safe contributors",
|
|
42
|
-
"license": "MIT",
|
|
43
|
-
"repository": {
|
|
44
|
-
"type": "git",
|
|
45
|
-
"url": "git+https://github.com/asamassekou10/ship-safe.git"
|
|
46
|
-
},
|
|
47
|
-
"bugs": {
|
|
48
|
-
"url": "https://github.com/asamassekou10/ship-safe/issues"
|
|
49
|
-
},
|
|
50
|
-
"homepage": "https://github.com/asamassekou10/ship-safe#readme",
|
|
51
|
-
"engines": {
|
|
52
|
-
"node": ">=18.0.0"
|
|
53
|
-
},
|
|
54
|
-
"files": [
|
|
55
|
-
"cli/",
|
|
56
|
-
"!cli/__tests__/",
|
|
57
|
-
"checklists/",
|
|
58
|
-
"configs/",
|
|
59
|
-
"snippets/",
|
|
60
|
-
"ai-defense/"
|
|
61
|
-
],
|
|
62
|
-
"dependencies": {
|
|
63
|
-
"chalk": "^5.3.0",
|
|
64
|
-
"commander": "^12.1.0",
|
|
65
|
-
"fast-glob": "^3.3.3",
|
|
66
|
-
"ora": "^8.0.1",
|
|
67
|
-
"write-file-atomic": "^7.0.0"
|
|
68
|
-
}
|
|
69
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "ship-safe",
|
|
3
|
+
"version": "6.2.0",
|
|
4
|
+
"description": "AI-powered multi-agent security platform. 18 agents scan 80+ attack classes with LLM-powered deep analysis. Red team your code before attackers do.",
|
|
5
|
+
"main": "cli/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ship-safe": "cli/bin/ship-safe.js"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "node --test cli/__tests__/*.test.js",
|
|
12
|
+
"lint": "eslint cli/",
|
|
13
|
+
"ship-safe": "node cli/bin/ship-safe.js"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"security",
|
|
17
|
+
"secrets",
|
|
18
|
+
"scanner",
|
|
19
|
+
"sast",
|
|
20
|
+
"devsecops",
|
|
21
|
+
"red-team",
|
|
22
|
+
"penetration-testing",
|
|
23
|
+
"vulnerability-scanner",
|
|
24
|
+
"sbom",
|
|
25
|
+
"owasp",
|
|
26
|
+
"sql-injection",
|
|
27
|
+
"xss",
|
|
28
|
+
"ssrf",
|
|
29
|
+
"supply-chain",
|
|
30
|
+
"llm-security",
|
|
31
|
+
"prompt-injection",
|
|
32
|
+
"api-security",
|
|
33
|
+
"docker-security",
|
|
34
|
+
"kubernetes",
|
|
35
|
+
"cicd-security",
|
|
36
|
+
"mobile-security",
|
|
37
|
+
"jwt",
|
|
38
|
+
"cors",
|
|
39
|
+
"cli"
|
|
40
|
+
],
|
|
41
|
+
"author": "ship-safe contributors",
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "git+https://github.com/asamassekou10/ship-safe.git"
|
|
46
|
+
},
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/asamassekou10/ship-safe/issues"
|
|
49
|
+
},
|
|
50
|
+
"homepage": "https://github.com/asamassekou10/ship-safe#readme",
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=18.0.0"
|
|
53
|
+
},
|
|
54
|
+
"files": [
|
|
55
|
+
"cli/",
|
|
56
|
+
"!cli/__tests__/",
|
|
57
|
+
"checklists/",
|
|
58
|
+
"configs/",
|
|
59
|
+
"snippets/",
|
|
60
|
+
"ai-defense/"
|
|
61
|
+
],
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"chalk": "^5.3.0",
|
|
64
|
+
"commander": "^12.1.0",
|
|
65
|
+
"fast-glob": "^3.3.3",
|
|
66
|
+
"ora": "^8.0.1",
|
|
67
|
+
"write-file-atomic": "^7.0.0"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
-- =============================================================================
|
|
2
|
-
-- SUPABASE ROW LEVEL SECURITY (RLS) TEMPLATES
|
|
3
|
-
-- =============================================================================
|
|
4
|
-
--
|
|
5
|
-
-- Copy-paste these policies to secure your Supabase tables.
|
|
6
|
-
--
|
|
7
|
-
-- WHY RLS MATTERS:
|
|
8
|
-
-- Without RLS, anyone with your anon key can read/write ALL data.
|
|
9
|
-
-- 83% of exposed Supabase databases have RLS misconfigurations.
|
|
10
|
-
-- Source: https://byteiota.com/supabase-security-flaw-170-apps-exposed-by-missing-rls/
|
|
11
|
-
--
|
|
12
|
-
-- HOW TO USE:
|
|
13
|
-
-- 1. Enable RLS on your table: ALTER TABLE your_table ENABLE ROW LEVEL SECURITY;
|
|
14
|
-
-- 2. Copy the relevant policy below
|
|
15
|
-
-- 3. Replace 'your_table' with your actual table name
|
|
16
|
-
-- 4. Test with different user contexts
|
|
17
|
-
--
|
|
18
|
-
-- =============================================================================
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
-- =============================================================================
|
|
22
|
-
-- STEP 1: ALWAYS ENABLE RLS FIRST
|
|
23
|
-
-- =============================================================================
|
|
24
|
-
|
|
25
|
-
-- Enable RLS on a table (REQUIRED before adding policies)
|
|
26
|
-
ALTER TABLE your_table ENABLE ROW LEVEL SECURITY;
|
|
27
|
-
|
|
28
|
-
-- Force RLS for table owners too (recommended for security)
|
|
29
|
-
ALTER TABLE your_table FORCE ROW LEVEL SECURITY;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
-- =============================================================================
|
|
33
|
-
-- PATTERN 1: USER OWNS THEIR DATA
|
|
34
|
-
-- =============================================================================
|
|
35
|
-
-- Use when: Each user should only see/edit their own records
|
|
36
|
-
-- Example tables: profiles, settings, user_preferences
|
|
37
|
-
|
|
38
|
-
-- Users can only SELECT their own rows
|
|
39
|
-
CREATE POLICY "Users can view own data"
|
|
40
|
-
ON your_table
|
|
41
|
-
FOR SELECT
|
|
42
|
-
USING (auth.uid() = user_id);
|
|
43
|
-
|
|
44
|
-
-- Users can only INSERT rows with their own user_id
|
|
45
|
-
CREATE POLICY "Users can insert own data"
|
|
46
|
-
ON your_table
|
|
47
|
-
FOR INSERT
|
|
48
|
-
WITH CHECK (auth.uid() = user_id);
|
|
49
|
-
|
|
50
|
-
-- Users can only UPDATE their own rows
|
|
51
|
-
CREATE POLICY "Users can update own data"
|
|
52
|
-
ON your_table
|
|
53
|
-
FOR UPDATE
|
|
54
|
-
USING (auth.uid() = user_id)
|
|
55
|
-
WITH CHECK (auth.uid() = user_id);
|
|
56
|
-
|
|
57
|
-
-- Users can only DELETE their own rows
|
|
58
|
-
CREATE POLICY "Users can delete own data"
|
|
59
|
-
ON your_table
|
|
60
|
-
FOR DELETE
|
|
61
|
-
USING (auth.uid() = user_id);
|
|
62
|
-
|
|
63
|
-
-- COMBINED: All operations for own data (simpler but less granular)
|
|
64
|
-
CREATE POLICY "Users manage own data"
|
|
65
|
-
ON your_table
|
|
66
|
-
FOR ALL
|
|
67
|
-
USING (auth.uid() = user_id)
|
|
68
|
-
WITH CHECK (auth.uid() = user_id);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
-- =============================================================================
|
|
72
|
-
-- PATTERN 2: ORGANIZATION/TEAM BASED ACCESS
|
|
73
|
-
-- =============================================================================
|
|
74
|
-
-- Use when: Users belong to orgs and should see all org data
|
|
75
|
-
-- Example tables: projects, documents, team_settings
|
|
76
|
-
|
|
77
|
-
-- First, create a helper function to get user's org
|
|
78
|
-
CREATE OR REPLACE FUNCTION get_user_org_id()
|
|
79
|
-
RETURNS UUID AS $$
|
|
80
|
-
SELECT org_id FROM profiles WHERE id = auth.uid()
|
|
81
|
-
$$ LANGUAGE SQL SECURITY DEFINER;
|
|
82
|
-
|
|
83
|
-
-- Users can view all data in their organization
|
|
84
|
-
CREATE POLICY "Org members can view org data"
|
|
85
|
-
ON your_table
|
|
86
|
-
FOR SELECT
|
|
87
|
-
USING (org_id = get_user_org_id());
|
|
88
|
-
|
|
89
|
-
-- Users can insert data into their organization
|
|
90
|
-
CREATE POLICY "Org members can insert org data"
|
|
91
|
-
ON your_table
|
|
92
|
-
FOR INSERT
|
|
93
|
-
WITH CHECK (org_id = get_user_org_id());
|
|
94
|
-
|
|
95
|
-
-- Only allow updates to org data
|
|
96
|
-
CREATE POLICY "Org members can update org data"
|
|
97
|
-
ON your_table
|
|
98
|
-
FOR UPDATE
|
|
99
|
-
USING (org_id = get_user_org_id())
|
|
100
|
-
WITH CHECK (org_id = get_user_org_id());
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
-- =============================================================================
|
|
104
|
-
-- PATTERN 3: ROLE-BASED ACCESS CONTROL (RBAC)
|
|
105
|
-
-- =============================================================================
|
|
106
|
-
-- Use when: Different users have different permission levels
|
|
107
|
-
-- Example: admin, editor, viewer roles
|
|
108
|
-
|
|
109
|
-
-- Helper function to check user role
|
|
110
|
-
CREATE OR REPLACE FUNCTION get_user_role()
|
|
111
|
-
RETURNS TEXT AS $$
|
|
112
|
-
SELECT role FROM profiles WHERE id = auth.uid()
|
|
113
|
-
$$ LANGUAGE SQL SECURITY DEFINER;
|
|
114
|
-
|
|
115
|
-
-- Anyone authenticated can view (public within app)
|
|
116
|
-
CREATE POLICY "Authenticated users can view"
|
|
117
|
-
ON your_table
|
|
118
|
-
FOR SELECT
|
|
119
|
-
USING (auth.role() = 'authenticated');
|
|
120
|
-
|
|
121
|
-
-- Only admins and editors can insert
|
|
122
|
-
CREATE POLICY "Admins and editors can insert"
|
|
123
|
-
ON your_table
|
|
124
|
-
FOR INSERT
|
|
125
|
-
WITH CHECK (get_user_role() IN ('admin', 'editor'));
|
|
126
|
-
|
|
127
|
-
-- Only admins can delete
|
|
128
|
-
CREATE POLICY "Only admins can delete"
|
|
129
|
-
ON your_table
|
|
130
|
-
FOR DELETE
|
|
131
|
-
USING (get_user_role() = 'admin');
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
-- =============================================================================
|
|
135
|
-
-- PATTERN 4: PUBLIC READ, AUTHENTICATED WRITE
|
|
136
|
-
-- =============================================================================
|
|
137
|
-
-- Use when: Content is public but only logged-in users can contribute
|
|
138
|
-
-- Example tables: blog_posts, comments, public_profiles
|
|
139
|
-
|
|
140
|
-
-- Anyone can read (including anonymous)
|
|
141
|
-
CREATE POLICY "Public read access"
|
|
142
|
-
ON your_table
|
|
143
|
-
FOR SELECT
|
|
144
|
-
USING (true);
|
|
145
|
-
|
|
146
|
-
-- Only authenticated users can insert
|
|
147
|
-
CREATE POLICY "Authenticated users can insert"
|
|
148
|
-
ON your_table
|
|
149
|
-
FOR INSERT
|
|
150
|
-
WITH CHECK (auth.role() = 'authenticated');
|
|
151
|
-
|
|
152
|
-
-- Users can only update their own posts
|
|
153
|
-
CREATE POLICY "Users can update own posts"
|
|
154
|
-
ON your_table
|
|
155
|
-
FOR UPDATE
|
|
156
|
-
USING (auth.uid() = author_id)
|
|
157
|
-
WITH CHECK (auth.uid() = author_id);
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
-- =============================================================================
|
|
161
|
-
-- PATTERN 5: PRIVATE BY DEFAULT (Explicit sharing)
|
|
162
|
-
-- =============================================================================
|
|
163
|
-
-- Use when: Data is private unless explicitly shared
|
|
164
|
-
-- Example tables: documents, files with sharing
|
|
165
|
-
|
|
166
|
-
-- Owner can always access
|
|
167
|
-
CREATE POLICY "Owner full access"
|
|
168
|
-
ON documents
|
|
169
|
-
FOR ALL
|
|
170
|
-
USING (auth.uid() = owner_id)
|
|
171
|
-
WITH CHECK (auth.uid() = owner_id);
|
|
172
|
-
|
|
173
|
-
-- Shared users can view (requires a shares table)
|
|
174
|
-
CREATE POLICY "Shared users can view"
|
|
175
|
-
ON documents
|
|
176
|
-
FOR SELECT
|
|
177
|
-
USING (
|
|
178
|
-
auth.uid() = owner_id
|
|
179
|
-
OR
|
|
180
|
-
EXISTS (
|
|
181
|
-
SELECT 1 FROM document_shares
|
|
182
|
-
WHERE document_shares.document_id = documents.id
|
|
183
|
-
AND document_shares.user_id = auth.uid()
|
|
184
|
-
)
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
-- =============================================================================
|
|
189
|
-
-- PATTERN 6: TIME-BASED ACCESS
|
|
190
|
-
-- =============================================================================
|
|
191
|
-
-- Use when: Content has publish dates or expiration
|
|
192
|
-
-- Example tables: scheduled_posts, limited_offers
|
|
193
|
-
|
|
194
|
-
-- Only show published content
|
|
195
|
-
CREATE POLICY "Show only published content"
|
|
196
|
-
ON posts
|
|
197
|
-
FOR SELECT
|
|
198
|
-
USING (
|
|
199
|
-
published_at IS NOT NULL
|
|
200
|
-
AND published_at <= NOW()
|
|
201
|
-
AND (expires_at IS NULL OR expires_at > NOW())
|
|
202
|
-
);
|
|
203
|
-
|
|
204
|
-
-- Authors can always see their drafts
|
|
205
|
-
CREATE POLICY "Authors see own drafts"
|
|
206
|
-
ON posts
|
|
207
|
-
FOR SELECT
|
|
208
|
-
USING (auth.uid() = author_id);
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
-- =============================================================================
|
|
212
|
-
-- ANTI-PATTERNS: DON'T DO THIS
|
|
213
|
-
-- =============================================================================
|
|
214
|
-
|
|
215
|
-
-- BAD: Allows anyone to read everything
|
|
216
|
-
-- CREATE POLICY "bad_policy" ON users FOR SELECT USING (true);
|
|
217
|
-
|
|
218
|
-
-- BAD: No WITH CHECK means users could insert data for other users
|
|
219
|
-
-- CREATE POLICY "bad_insert" ON posts FOR INSERT WITH CHECK (true);
|
|
220
|
-
|
|
221
|
-
-- BAD: Missing USING clause on UPDATE allows updating any row
|
|
222
|
-
-- CREATE POLICY "bad_update" ON posts FOR UPDATE WITH CHECK (auth.uid() = user_id);
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
-- =============================================================================
|
|
226
|
-
-- TESTING YOUR POLICIES
|
|
227
|
-
-- =============================================================================
|
|
228
|
-
|
|
229
|
-
-- Test as a specific user (in SQL editor)
|
|
230
|
-
-- SET request.jwt.claim.sub = 'user-uuid-here';
|
|
231
|
-
-- SELECT * FROM your_table;
|
|
232
|
-
|
|
233
|
-
-- Check existing policies on a table
|
|
234
|
-
SELECT * FROM pg_policies WHERE tablename = 'your_table';
|
|
235
|
-
|
|
236
|
-
-- List all tables without RLS enabled (DANGER!)
|
|
237
|
-
SELECT schemaname, tablename
|
|
238
|
-
FROM pg_tables
|
|
239
|
-
WHERE schemaname = 'public'
|
|
240
|
-
AND tablename NOT IN (
|
|
241
|
-
SELECT tablename FROM pg_policies
|
|
242
|
-
);
|