secure-repo 1.0.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/LICENSE +26 -0
- package/README.md +212 -0
- package/bin/cli.js +589 -0
- package/package.json +34 -0
- package/templates/free/API.md +111 -0
- package/templates/free/AUTH.md +99 -0
- package/templates/free/SECURITY.md +117 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Secure Repo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
NOTE: This MIT license applies to the free templates (templates/free/).
|
|
26
|
+
Pro, premium, and preset templates are licensed separately under purchase terms.
|
package/README.md
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# Secure Repo
|
|
2
|
+
|
|
3
|
+
**Drop production-grade security standards into any repository in 30 seconds.**
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx secure-repo init
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
That's it. Your repo now has battle-tested security policies, checklists, and enforcement rules used in real production SaaS applications.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Audit Your Repo
|
|
14
|
+
|
|
15
|
+
Not sure where you stand? Run an instant security audit:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx secure-repo audit
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
secure-repo audit
|
|
23
|
+
|
|
24
|
+
Scanning repository for security issues...
|
|
25
|
+
|
|
26
|
+
Policy files:
|
|
27
|
+
[FAIL] SECURITY.md — missing (high priority)
|
|
28
|
+
[FAIL] AUTH.md — missing (high priority)
|
|
29
|
+
[FAIL] API.md — missing (high priority)
|
|
30
|
+
[warn] DATABASE.md — missing
|
|
31
|
+
[warn] DEPLOYMENT.md — missing
|
|
32
|
+
|
|
33
|
+
Environment files:
|
|
34
|
+
[pass] .env is in .gitignore
|
|
35
|
+
[pass] .env.example exists
|
|
36
|
+
|
|
37
|
+
Secret scanning:
|
|
38
|
+
[pass] No obvious secrets found in source files
|
|
39
|
+
|
|
40
|
+
────────────────────────────────────
|
|
41
|
+
Results: 3 passed, 2 warnings, 3 issues
|
|
42
|
+
────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
3 issue(s) found. Fix these before shipping.
|
|
45
|
+
Run: npx secure-repo init
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Zero setup. Zero dependencies. Just run it.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## The Problem
|
|
53
|
+
|
|
54
|
+
You're building a SaaS app. You know you should have security policies, but:
|
|
55
|
+
|
|
56
|
+
- Writing them from scratch takes days
|
|
57
|
+
- You don't know what you're missing until something breaks
|
|
58
|
+
- AI coding agents generate insecure code when there's no policy file to guide them
|
|
59
|
+
- Your team has no shared standard for "how we handle auth" or "what counts as a safe API endpoint"
|
|
60
|
+
|
|
61
|
+
## The Solution
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npx secure-repo init
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
secure-repo - Adding production standards to your project
|
|
69
|
+
|
|
70
|
+
Free templates:
|
|
71
|
+
[done] SECURITY.md
|
|
72
|
+
[done] AUTH.md
|
|
73
|
+
[done] API.md
|
|
74
|
+
|
|
75
|
+
Done! 3 files added.
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Every template is:
|
|
79
|
+
|
|
80
|
+
- **Opinionated** — makes decisions so you don't have to
|
|
81
|
+
- **Actionable** — every rule is verifiable, every pattern is copy-pastable
|
|
82
|
+
- **AI-agent friendly** — coding agents read these files and write safer code
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## What You Get (Free)
|
|
87
|
+
|
|
88
|
+
Three templates that cover the foundations:
|
|
89
|
+
|
|
90
|
+
**SECURITY.md** — No secrets in code. Privileged keys server-side only. Database access control mandatory. Server endpoints for all writes. Incident response steps.
|
|
91
|
+
|
|
92
|
+
**AUTH.md** — JWT verification rules. Token storage (httpOnly cookies, not localStorage). Password hashing (bcrypt/argon2). Rate limiting on login. Session revocation. Role-based access.
|
|
93
|
+
|
|
94
|
+
**API.md** — Input validation on every endpoint. Rate limiting on all public routes. Error responses that don't leak internals. Endpoint conventions. CORS rules. Pagination enforcement.
|
|
95
|
+
|
|
96
|
+
Each file includes:
|
|
97
|
+
- Rules marked "MUST FOLLOW"
|
|
98
|
+
- Code patterns to copy
|
|
99
|
+
- A "Required Checks Before Merge" checklist
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Pro Pack
|
|
104
|
+
|
|
105
|
+
27 additional files for teams that want complete coverage. Sold separately.
|
|
106
|
+
|
|
107
|
+
### Pro Templates (18 files)
|
|
108
|
+
|
|
109
|
+
| Template | What it covers |
|
|
110
|
+
|----------|---------------|
|
|
111
|
+
| `DATABASE.md` | Access control patterns, migration safety, table design |
|
|
112
|
+
| `DEPLOYMENT.md` | CI/CD pipeline, rollback procedures, zero-downtime deploys |
|
|
113
|
+
| `INCIDENT_RESPONSE.md` | Severity levels, response steps, post-mortem template, runbooks |
|
|
114
|
+
| `OBSERVABILITY.md` | Structured logging, PII redaction, alerting rules, health checks |
|
|
115
|
+
| `TESTING.md` | Security test patterns with code examples |
|
|
116
|
+
| `ENV_VARIABLES.md` | Secrets management, rotation, client vs server vars |
|
|
117
|
+
| `PAYMENTS.md` | Stripe webhooks, subscription management, PCI rules |
|
|
118
|
+
| `DATA_PRIVACY.md` | GDPR, retention schedules, account deletion flow |
|
|
119
|
+
| `FILE_UPLOADS.md` | Upload validation, signed URLs, serving rules |
|
|
120
|
+
| `RATE_LIMITING.md` | Per-endpoint limits, implementation patterns |
|
|
121
|
+
| `THIRD_PARTY.md` | Integration inventory, webhook security, OAuth |
|
|
122
|
+
| `ACCESS_CONTROL.md` | Roles, permissions, escalation, break-glass procedures |
|
|
123
|
+
| `LOGGING_PII.md` | What to log, what to redact, compliance rules |
|
|
124
|
+
| `PR_CHECKLIST.md` | Copy-paste security checklist for every PR |
|
|
125
|
+
| `THREAT_MODEL.md` | Lightweight threat modeling template |
|
|
126
|
+
| `VULNERABILITY_REPORTING.md` | Responsible disclosure policy |
|
|
127
|
+
| `CONTRIBUTING_SECURITY.md` | Contributor security rules |
|
|
128
|
+
| `POLICY_INDEX.md` | One-page index linking all policies |
|
|
129
|
+
|
|
130
|
+
### Premium: 100+ Point Security Audit
|
|
131
|
+
|
|
132
|
+
`FULL_AUDIT_CHECKLIST.md` — Complete production security audit with severity ratings, explanations for every item, and an audit summary template. This is what security consultants charge thousands to produce.
|
|
133
|
+
|
|
134
|
+
### Stack Presets
|
|
135
|
+
|
|
136
|
+
- **Supabase** — RLS policies, `auth.uid()` patterns, service role rules, function hardening (6 files)
|
|
137
|
+
- **Firebase** — Firestore security rules, Firebase Auth, Cloud Functions, custom claims (3 files)
|
|
138
|
+
|
|
139
|
+
### Code Examples
|
|
140
|
+
|
|
141
|
+
- `next-route-handler.ts` — Secure API route with auth + validation + error handling
|
|
142
|
+
- `rate-limit.ts` — Rate limiting middleware (in-memory + Redis)
|
|
143
|
+
- `zod-validate.ts` — Reusable validation schemas
|
|
144
|
+
- `supabase-rls.sql` — 8 RLS policy patterns
|
|
145
|
+
- `firebase-rules.txt` — 8 Firestore rule patterns
|
|
146
|
+
|
|
147
|
+
**Get the pro pack:** [polar.sh/third-space-labs](https://polar.sh/third-space-labs)
|
|
148
|
+
|
|
149
|
+
After purchase, install with one command:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
npx secure-repo init --key <your-license-key>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Commands
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
npx secure-repo init # Add free security templates
|
|
161
|
+
npx secure-repo init --key <key> # Add free + pro templates (with license key)
|
|
162
|
+
npx secure-repo audit # Scan your repo for security issues
|
|
163
|
+
npx secure-repo import <zip> # Import pro templates from zip (offline)
|
|
164
|
+
npx secure-repo check # Check if your templates are outdated
|
|
165
|
+
npx secure-repo list # Show all available templates
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Free vs Pro
|
|
171
|
+
|
|
172
|
+
| | Free | Pro Pack |
|
|
173
|
+
|--|------|---------|
|
|
174
|
+
| Security audit command | Included | Included |
|
|
175
|
+
| Core security policies (3 files) | Included | Included |
|
|
176
|
+
| Deep engineering standards (18 files) | - | Included |
|
|
177
|
+
| 100+ point audit checklist | - | Included |
|
|
178
|
+
| Stack presets (Supabase, Firebase) | - | Included |
|
|
179
|
+
| Code examples (5 files) | - | Included |
|
|
180
|
+
| **Total policy files** | **3** | **30** |
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Why This Matters for AI-Assisted Development
|
|
185
|
+
|
|
186
|
+
If you use Cursor, Claude Code, Copilot, or any AI coding agent — these files change how the agent writes code in your repo.
|
|
187
|
+
|
|
188
|
+
When an AI agent sees `SECURITY.md` in your project root, it follows those rules. When it sees `API.md`, it validates input and handles errors correctly. When it sees `AUTH.md`, it checks tokens server-side.
|
|
189
|
+
|
|
190
|
+
**Without policy files:** The agent guesses. It writes code that works but may not be secure.
|
|
191
|
+
|
|
192
|
+
**With policy files:** The agent follows your standards. Every generated endpoint validates input, checks auth, and handles errors safely.
|
|
193
|
+
|
|
194
|
+
Secure Repo gives your AI agents the rules they need to write production-safe code.
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Support This Project
|
|
199
|
+
|
|
200
|
+
If Secure Repo helps you ship safer software, consider [sponsoring development](https://github.com/sponsors/sebiomoa).
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## License
|
|
205
|
+
|
|
206
|
+
Free templates (`templates/free/`) are [MIT licensed](LICENSE).
|
|
207
|
+
|
|
208
|
+
Pro pack templates are licensed for personal and commercial use by the purchaser.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
*This is not legal advice. These templates do not replace professional security audits for regulated industries. They are practical engineering tools for building more secure applications.*
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const https = require("https");
|
|
6
|
+
const { execSync } = require("child_process");
|
|
7
|
+
|
|
8
|
+
const TEMPLATES_DIR = path.join(__dirname, "..", "templates");
|
|
9
|
+
const FREE_DIR = path.join(TEMPLATES_DIR, "free");
|
|
10
|
+
|
|
11
|
+
const POLAR_ORGANIZATION_ID = "d55baa70-3a94-4549-901a-2b4c920ff122";
|
|
12
|
+
|
|
13
|
+
const PRO_ZIP_URL = "https://github.com/sebiomoa/secure-repo/releases/latest/download/secure-repo-pro.zip";
|
|
14
|
+
|
|
15
|
+
const args = process.argv.slice(2);
|
|
16
|
+
const command = args[0];
|
|
17
|
+
|
|
18
|
+
// Files that a secure repo should have
|
|
19
|
+
const RECOMMENDED_FILES = [
|
|
20
|
+
{ file: "SECURITY.md", category: "security", severity: "high" },
|
|
21
|
+
{ file: "AUTH.md", category: "security", severity: "high" },
|
|
22
|
+
{ file: "API.md", category: "security", severity: "high" },
|
|
23
|
+
{ file: "DATABASE.md", category: "security", severity: "medium" },
|
|
24
|
+
{ file: "DEPLOYMENT.md", category: "operations", severity: "medium" },
|
|
25
|
+
{ file: "INCIDENT_RESPONSE.md", category: "operations", severity: "medium" },
|
|
26
|
+
{ file: "ENV_VARIABLES.md", category: "security", severity: "high" },
|
|
27
|
+
{ file: "CONTRIBUTING.md", category: "process", severity: "low" },
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
// Known dangerous patterns to scan for
|
|
31
|
+
const DANGER_PATTERNS = [
|
|
32
|
+
{ pattern: /sk_live_[a-zA-Z0-9]+/, label: "Stripe live secret key" },
|
|
33
|
+
{ pattern: /sk_test_[a-zA-Z0-9]+/, label: "Stripe test secret key" },
|
|
34
|
+
{ pattern: /eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+/, label: "JWT token" },
|
|
35
|
+
{ pattern: /SUPABASE_SERVICE_ROLE_KEY/, label: "Supabase service role key reference" },
|
|
36
|
+
{ pattern: /password\s*[:=]\s*['"][^'"]+['"]/, label: "Hardcoded password" },
|
|
37
|
+
{ pattern: /api[_-]?key\s*[:=]\s*['"][^'"]+['"]/, label: "Hardcoded API key" },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
function printHelp() {
|
|
41
|
+
console.log(`
|
|
42
|
+
secure-repo - Production-grade security standards for your repo
|
|
43
|
+
|
|
44
|
+
Usage:
|
|
45
|
+
npx secure-repo init Add free security templates
|
|
46
|
+
npx secure-repo init --key <key> Add free + pro templates (requires license key)
|
|
47
|
+
npx secure-repo audit Scan your repo for security issues
|
|
48
|
+
npx secure-repo import <file> Import pro templates from a zip file (offline)
|
|
49
|
+
npx secure-repo check Check which templates are outdated
|
|
50
|
+
npx secure-repo list Show available free templates
|
|
51
|
+
|
|
52
|
+
Options:
|
|
53
|
+
--key Your license key (from purchase)
|
|
54
|
+
--force Overwrite existing files
|
|
55
|
+
--output Output directory (default: current directory)
|
|
56
|
+
|
|
57
|
+
Free templates (always included):
|
|
58
|
+
SECURITY.md Secrets management, attack surface, enforced architecture
|
|
59
|
+
AUTH.md Token handling, session rules, password policy, roles
|
|
60
|
+
API.md Input validation, rate limiting, error handling
|
|
61
|
+
|
|
62
|
+
Pro templates (purchase at https://polar.sh/third-space-labs):
|
|
63
|
+
30 additional files — templates, audit checklist, stack presets, examples
|
|
64
|
+
Install with: npx secure-repo init --key <your-license-key>
|
|
65
|
+
`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function listTemplates() {
|
|
69
|
+
console.log("\n Free templates (included):\n");
|
|
70
|
+
if (fs.existsSync(FREE_DIR)) {
|
|
71
|
+
fs.readdirSync(FREE_DIR).forEach((f) => console.log(` ${f}`));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
console.log("\n Pro templates (sold separately):\n");
|
|
75
|
+
const proFiles = [
|
|
76
|
+
"DATABASE.md", "DEPLOYMENT.md", "INCIDENT_RESPONSE.md", "OBSERVABILITY.md",
|
|
77
|
+
"TESTING.md", "ENV_VARIABLES.md", "PAYMENTS.md", "DATA_PRIVACY.md",
|
|
78
|
+
"FILE_UPLOADS.md", "RATE_LIMITING.md", "THIRD_PARTY.md", "ACCESS_CONTROL.md",
|
|
79
|
+
"LOGGING_PII.md", "PR_CHECKLIST.md", "THREAT_MODEL.md",
|
|
80
|
+
"VULNERABILITY_REPORTING.md", "CONTRIBUTING_SECURITY.md", "POLICY_INDEX.md",
|
|
81
|
+
];
|
|
82
|
+
proFiles.forEach((f) => console.log(` ${f}`));
|
|
83
|
+
|
|
84
|
+
console.log("\n Premium:\n");
|
|
85
|
+
console.log(" FULL_AUDIT_CHECKLIST.md (100+ point security audit)");
|
|
86
|
+
|
|
87
|
+
console.log("\n Stack presets:\n");
|
|
88
|
+
console.log(" supabase/ (6 files)");
|
|
89
|
+
console.log(" firebase/ (3 files)");
|
|
90
|
+
|
|
91
|
+
console.log("\n Code examples:\n");
|
|
92
|
+
console.log(" next-route-handler.ts, rate-limit.ts, zod-validate.ts");
|
|
93
|
+
console.log(" supabase-rls.sql, firebase-rules.txt");
|
|
94
|
+
|
|
95
|
+
console.log("\n Get pro: https://polar.sh/third-space-labs\n");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function getArg(flag) {
|
|
99
|
+
const idx = args.indexOf(flag);
|
|
100
|
+
if (idx === -1 || idx + 1 >= args.length) return null;
|
|
101
|
+
return args[idx + 1];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function copyFiles(srcDir, destDir, force) {
|
|
105
|
+
if (!fs.existsSync(srcDir)) return { copied: 0, skipped: 0 };
|
|
106
|
+
if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
|
|
107
|
+
|
|
108
|
+
const files = fs.readdirSync(srcDir);
|
|
109
|
+
let copied = 0;
|
|
110
|
+
let skipped = 0;
|
|
111
|
+
|
|
112
|
+
files.forEach((file) => {
|
|
113
|
+
const srcPath = path.join(srcDir, file);
|
|
114
|
+
if (fs.statSync(srcPath).isDirectory()) return;
|
|
115
|
+
|
|
116
|
+
const destPath = path.join(destDir, file);
|
|
117
|
+
if (fs.existsSync(destPath) && !force) {
|
|
118
|
+
console.log(` [skip] ${file} (use --force to overwrite)`);
|
|
119
|
+
skipped++;
|
|
120
|
+
} else {
|
|
121
|
+
fs.copyFileSync(srcPath, destPath);
|
|
122
|
+
console.log(` [done] ${file}`);
|
|
123
|
+
copied++;
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
return { copied, skipped };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ============================================================
|
|
131
|
+
// Polar license verification
|
|
132
|
+
// ============================================================
|
|
133
|
+
function verifyLicense(licenseKey) {
|
|
134
|
+
return new Promise((resolve, reject) => {
|
|
135
|
+
const postData = JSON.stringify({
|
|
136
|
+
key: licenseKey,
|
|
137
|
+
organization_id: POLAR_ORGANIZATION_ID,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const req = https.request(
|
|
141
|
+
{
|
|
142
|
+
hostname: "api.polar.sh",
|
|
143
|
+
path: "/v1/users/license-keys/validate",
|
|
144
|
+
method: "POST",
|
|
145
|
+
headers: {
|
|
146
|
+
"Content-Type": "application/json",
|
|
147
|
+
"Content-Length": Buffer.byteLength(postData),
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
(res) => {
|
|
151
|
+
let data = "";
|
|
152
|
+
res.on("data", (chunk) => (data += chunk));
|
|
153
|
+
res.on("end", () => {
|
|
154
|
+
try {
|
|
155
|
+
const json = JSON.parse(data);
|
|
156
|
+
if (json.status === "granted" || json.status === "active") {
|
|
157
|
+
resolve(json);
|
|
158
|
+
} else {
|
|
159
|
+
const msg = typeof json.detail === "string" ? json.detail : "Invalid license key";
|
|
160
|
+
reject(new Error(msg));
|
|
161
|
+
}
|
|
162
|
+
} catch {
|
|
163
|
+
reject(new Error("Failed to verify license"));
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
req.on("error", (err) => reject(new Error(`Network error: ${err.message}`)));
|
|
170
|
+
req.write(postData);
|
|
171
|
+
req.end();
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ============================================================
|
|
176
|
+
// Download file from URL
|
|
177
|
+
// ============================================================
|
|
178
|
+
function downloadFile(url, destPath) {
|
|
179
|
+
return new Promise((resolve, reject) => {
|
|
180
|
+
const file = fs.createWriteStream(destPath);
|
|
181
|
+
|
|
182
|
+
function follow(url) {
|
|
183
|
+
https.get(url, (res) => {
|
|
184
|
+
// Follow redirects
|
|
185
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
186
|
+
follow(res.headers.location);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (res.statusCode !== 200) {
|
|
191
|
+
reject(new Error(`Download failed (HTTP ${res.statusCode})`));
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
res.pipe(file);
|
|
196
|
+
file.on("finish", () => {
|
|
197
|
+
file.close();
|
|
198
|
+
resolve();
|
|
199
|
+
});
|
|
200
|
+
}).on("error", (err) => {
|
|
201
|
+
fs.unlink(destPath, () => {});
|
|
202
|
+
reject(new Error(`Download error: ${err.message}`));
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
follow(url);
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ============================================================
|
|
211
|
+
// Extract zip and install pro templates
|
|
212
|
+
// ============================================================
|
|
213
|
+
function installFromZip(zipPath, outputDir, force) {
|
|
214
|
+
const tempDir = path.join(outputDir, ".secure-repo-temp");
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
218
|
+
execSync(`unzip -o "${zipPath}" -d "${tempDir}"`, { stdio: "pipe" });
|
|
219
|
+
|
|
220
|
+
let totalCopied = 0;
|
|
221
|
+
let totalSkipped = 0;
|
|
222
|
+
|
|
223
|
+
// Copy pro templates
|
|
224
|
+
const proDir = path.join(tempDir, "pro");
|
|
225
|
+
if (fs.existsSync(proDir)) {
|
|
226
|
+
console.log("\n Pro templates:");
|
|
227
|
+
const r = copyFiles(proDir, outputDir, force);
|
|
228
|
+
totalCopied += r.copied;
|
|
229
|
+
totalSkipped += r.skipped;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Copy premium templates
|
|
233
|
+
const premiumDir = path.join(tempDir, "premium");
|
|
234
|
+
if (fs.existsSync(premiumDir)) {
|
|
235
|
+
console.log("\n Premium:");
|
|
236
|
+
const r = copyFiles(premiumDir, outputDir, force);
|
|
237
|
+
totalCopied += r.copied;
|
|
238
|
+
totalSkipped += r.skipped;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Copy presets
|
|
242
|
+
const presetsDir = path.join(tempDir, "presets");
|
|
243
|
+
if (fs.existsSync(presetsDir)) {
|
|
244
|
+
const presets = fs.readdirSync(presetsDir).filter((f) =>
|
|
245
|
+
fs.statSync(path.join(presetsDir, f)).isDirectory()
|
|
246
|
+
);
|
|
247
|
+
presets.forEach((preset) => {
|
|
248
|
+
const presetDest = path.join(outputDir, `${preset}-preset`);
|
|
249
|
+
console.log(`\n Preset: ${preset} (-> ${preset}-preset/)`);
|
|
250
|
+
const r = copyFiles(path.join(presetsDir, preset), presetDest, force);
|
|
251
|
+
totalCopied += r.copied;
|
|
252
|
+
totalSkipped += r.skipped;
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Copy examples
|
|
257
|
+
const examplesDir = path.join(tempDir, "examples");
|
|
258
|
+
if (fs.existsSync(examplesDir)) {
|
|
259
|
+
const examplesDest = path.join(outputDir, "examples");
|
|
260
|
+
console.log("\n Code examples (-> examples/):");
|
|
261
|
+
const r = copyFiles(examplesDir, examplesDest, force);
|
|
262
|
+
totalCopied += r.copied;
|
|
263
|
+
totalSkipped += r.skipped;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return { copied: totalCopied, skipped: totalSkipped };
|
|
267
|
+
} finally {
|
|
268
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ============================================================
|
|
273
|
+
// INIT — install templates (free, or free + pro with --key)
|
|
274
|
+
// ============================================================
|
|
275
|
+
async function init() {
|
|
276
|
+
const force = args.includes("--force");
|
|
277
|
+
const outputDir = getArg("--output") || process.cwd();
|
|
278
|
+
const licenseKey = getArg("--key");
|
|
279
|
+
|
|
280
|
+
if (licenseKey) {
|
|
281
|
+
// Pro install: verify key, download, extract
|
|
282
|
+
console.log("\n secure-repo - Installing pro templates\n");
|
|
283
|
+
console.log(" Verifying license key...");
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
await verifyLicense(licenseKey);
|
|
287
|
+
console.log(" License valid!\n");
|
|
288
|
+
} catch (err) {
|
|
289
|
+
console.log(`\n License verification failed: ${err.message}`);
|
|
290
|
+
console.log(" Purchase at: https://polar.sh/third-space-labs\n");
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Install free templates first
|
|
295
|
+
console.log(" Free templates:");
|
|
296
|
+
const freeResult = copyFiles(FREE_DIR, outputDir, force);
|
|
297
|
+
|
|
298
|
+
// Download and install pro templates
|
|
299
|
+
const zipPath = path.join(outputDir, ".secure-repo-pro.zip");
|
|
300
|
+
console.log("\n Downloading pro templates...");
|
|
301
|
+
|
|
302
|
+
try {
|
|
303
|
+
await downloadFile(PRO_ZIP_URL, zipPath);
|
|
304
|
+
const proResult = installFromZip(zipPath, outputDir, force);
|
|
305
|
+
|
|
306
|
+
const totalCopied = freeResult.copied + proResult.copied;
|
|
307
|
+
const totalSkipped = freeResult.skipped + proResult.skipped;
|
|
308
|
+
|
|
309
|
+
console.log(`\n Done! ${totalCopied} files installed, ${totalSkipped} skipped.`);
|
|
310
|
+
console.log("\n Next steps:");
|
|
311
|
+
console.log(" 1. Customize the templates for your project");
|
|
312
|
+
console.log(" 2. Run: npx secure-repo audit");
|
|
313
|
+
console.log();
|
|
314
|
+
} catch (err) {
|
|
315
|
+
console.log(`\n Download failed: ${err.message}`);
|
|
316
|
+
console.log(" Try offline install: npx secure-repo import <zip-file>\n");
|
|
317
|
+
process.exit(1);
|
|
318
|
+
} finally {
|
|
319
|
+
// Clean up downloaded zip
|
|
320
|
+
if (fs.existsSync(zipPath)) fs.unlinkSync(zipPath);
|
|
321
|
+
}
|
|
322
|
+
} else {
|
|
323
|
+
// Free install
|
|
324
|
+
console.log("\n secure-repo - Adding production standards to your project\n");
|
|
325
|
+
|
|
326
|
+
console.log(" Free templates:");
|
|
327
|
+
const result = copyFiles(FREE_DIR, outputDir, force);
|
|
328
|
+
|
|
329
|
+
console.log(`\n Done! ${result.copied} files added, ${result.skipped} skipped.`);
|
|
330
|
+
console.log("\n Next steps:");
|
|
331
|
+
console.log(" 1. Customize the templates for your project");
|
|
332
|
+
console.log(" 2. Run: npx secure-repo audit");
|
|
333
|
+
console.log(" 3. Get pro templates: npx secure-repo init --key <your-key>");
|
|
334
|
+
console.log(" Purchase at: https://polar.sh/third-space-labs");
|
|
335
|
+
console.log();
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// ============================================================
|
|
340
|
+
// IMPORT — import pro templates from zip (offline fallback)
|
|
341
|
+
// ============================================================
|
|
342
|
+
function importPack() {
|
|
343
|
+
const zipPath = args[1];
|
|
344
|
+
|
|
345
|
+
if (!zipPath) {
|
|
346
|
+
console.log("\n Usage: npx secure-repo import <path-to-zip>\n");
|
|
347
|
+
console.log(" Offline alternative to: npx secure-repo init --key <key>");
|
|
348
|
+
console.log(" Get the pro pack at: https://polar.sh/third-space-labs\n");
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const resolvedPath = path.resolve(zipPath);
|
|
353
|
+
|
|
354
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
355
|
+
console.log(`\n File not found: ${resolvedPath}\n`);
|
|
356
|
+
process.exit(1);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const force = args.includes("--force");
|
|
360
|
+
const outputDir = getArg("--output") || process.cwd();
|
|
361
|
+
|
|
362
|
+
console.log("\n secure-repo - Importing pro templates\n");
|
|
363
|
+
|
|
364
|
+
// Install free templates too
|
|
365
|
+
console.log(" Free templates:");
|
|
366
|
+
const freeResult = copyFiles(FREE_DIR, outputDir, force);
|
|
367
|
+
|
|
368
|
+
try {
|
|
369
|
+
const proResult = installFromZip(resolvedPath, outputDir, force);
|
|
370
|
+
|
|
371
|
+
const totalCopied = freeResult.copied + proResult.copied;
|
|
372
|
+
const totalSkipped = freeResult.skipped + proResult.skipped;
|
|
373
|
+
|
|
374
|
+
console.log(`\n Done! ${totalCopied} files imported, ${totalSkipped} skipped.\n`);
|
|
375
|
+
} catch (err) {
|
|
376
|
+
console.log(`\n Failed to extract zip: ${err.message}\n`);
|
|
377
|
+
process.exit(1);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// ============================================================
|
|
382
|
+
// AUDIT — scan repo for security issues (the viral command)
|
|
383
|
+
// ============================================================
|
|
384
|
+
function audit() {
|
|
385
|
+
const targetDir = getArg("--output") || process.cwd();
|
|
386
|
+
|
|
387
|
+
console.log("\n secure-repo audit\n");
|
|
388
|
+
console.log(" Scanning repository for security issues...\n");
|
|
389
|
+
|
|
390
|
+
let issues = 0;
|
|
391
|
+
let warnings = 0;
|
|
392
|
+
let passed = 0;
|
|
393
|
+
|
|
394
|
+
// --- Check for recommended files ---
|
|
395
|
+
console.log(" Policy files:");
|
|
396
|
+
RECOMMENDED_FILES.forEach(({ file, severity }) => {
|
|
397
|
+
const filePath = path.join(targetDir, file);
|
|
398
|
+
if (fs.existsSync(filePath)) {
|
|
399
|
+
console.log(` [pass] ${file}`);
|
|
400
|
+
passed++;
|
|
401
|
+
} else if (severity === "high") {
|
|
402
|
+
console.log(` [FAIL] ${file} — missing (high priority)`);
|
|
403
|
+
issues++;
|
|
404
|
+
} else {
|
|
405
|
+
console.log(` [warn] ${file} — missing`);
|
|
406
|
+
warnings++;
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
// --- Check .gitignore for .env ---
|
|
411
|
+
console.log("\n Environment files:");
|
|
412
|
+
const gitignorePath = path.join(targetDir, ".gitignore");
|
|
413
|
+
if (fs.existsSync(gitignorePath)) {
|
|
414
|
+
const gitignore = fs.readFileSync(gitignorePath, "utf8");
|
|
415
|
+
if (gitignore.includes(".env")) {
|
|
416
|
+
console.log(" [pass] .env is in .gitignore");
|
|
417
|
+
passed++;
|
|
418
|
+
} else {
|
|
419
|
+
console.log(" [FAIL] .env is NOT in .gitignore");
|
|
420
|
+
issues++;
|
|
421
|
+
}
|
|
422
|
+
} else {
|
|
423
|
+
console.log(" [FAIL] No .gitignore found");
|
|
424
|
+
issues++;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// --- Check for committed .env files ---
|
|
428
|
+
const envFiles = [".env", ".env.local", ".env.production"];
|
|
429
|
+
envFiles.forEach((envFile) => {
|
|
430
|
+
const envPath = path.join(targetDir, envFile);
|
|
431
|
+
if (fs.existsSync(envPath)) {
|
|
432
|
+
console.log(` [FAIL] ${envFile} exists in repo — may contain secrets`);
|
|
433
|
+
issues++;
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
// --- Check for .env.example ---
|
|
438
|
+
const envExamplePath = path.join(targetDir, ".env.example");
|
|
439
|
+
if (fs.existsSync(envExamplePath)) {
|
|
440
|
+
console.log(" [pass] .env.example exists");
|
|
441
|
+
passed++;
|
|
442
|
+
} else {
|
|
443
|
+
console.log(" [warn] .env.example missing — document required env vars");
|
|
444
|
+
warnings++;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// --- Scan for hardcoded secrets in common files ---
|
|
448
|
+
console.log("\n Secret scanning:");
|
|
449
|
+
const scanExtensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".env.example"];
|
|
450
|
+
let secretsFound = 0;
|
|
451
|
+
|
|
452
|
+
function scanDir(dir, depth) {
|
|
453
|
+
if (depth > 5) return;
|
|
454
|
+
if (!fs.existsSync(dir)) return;
|
|
455
|
+
|
|
456
|
+
const entries = fs.readdirSync(dir);
|
|
457
|
+
entries.forEach((entry) => {
|
|
458
|
+
if (entry.startsWith(".") || entry === "node_modules" || entry === ".next" || entry === "bin" || entry === "lib") return;
|
|
459
|
+
const fullPath = path.join(dir, entry);
|
|
460
|
+
|
|
461
|
+
try {
|
|
462
|
+
const stat = fs.statSync(fullPath);
|
|
463
|
+
if (stat.isDirectory()) {
|
|
464
|
+
scanDir(fullPath, depth + 1);
|
|
465
|
+
} else if (scanExtensions.some((ext) => entry.endsWith(ext))) {
|
|
466
|
+
const content = fs.readFileSync(fullPath, "utf8");
|
|
467
|
+
DANGER_PATTERNS.forEach(({ pattern, label }) => {
|
|
468
|
+
if (pattern.test(content)) {
|
|
469
|
+
const relative = path.relative(targetDir, fullPath);
|
|
470
|
+
console.log(` [FAIL] ${relative} — ${label}`);
|
|
471
|
+
secretsFound++;
|
|
472
|
+
issues++;
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
} catch {
|
|
477
|
+
// skip unreadable files
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
scanDir(targetDir, 0);
|
|
483
|
+
|
|
484
|
+
if (secretsFound === 0) {
|
|
485
|
+
console.log(" [pass] No obvious secrets found in source files");
|
|
486
|
+
passed++;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// --- Check for HTTPS enforcement (look for http:// in env example) ---
|
|
490
|
+
console.log("\n Configuration:");
|
|
491
|
+
if (fs.existsSync(envExamplePath)) {
|
|
492
|
+
const envExample = fs.readFileSync(envExamplePath, "utf8");
|
|
493
|
+
if (envExample.includes("http://") && !envExample.includes("localhost")) {
|
|
494
|
+
console.log(" [warn] .env.example contains http:// URLs (should be https://)");
|
|
495
|
+
warnings++;
|
|
496
|
+
} else {
|
|
497
|
+
console.log(" [pass] No insecure URLs in .env.example");
|
|
498
|
+
passed++;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// --- Check for package-lock.json or equivalent ---
|
|
503
|
+
const lockFiles = ["package-lock.json", "yarn.lock", "pnpm-lock.yaml", "bun.lockb"];
|
|
504
|
+
const hasLockFile = lockFiles.some((f) => fs.existsSync(path.join(targetDir, f)));
|
|
505
|
+
if (hasLockFile) {
|
|
506
|
+
console.log(" [pass] Dependency lock file exists");
|
|
507
|
+
passed++;
|
|
508
|
+
} else if (fs.existsSync(path.join(targetDir, "package.json"))) {
|
|
509
|
+
console.log(" [warn] No lock file found — pin your dependencies");
|
|
510
|
+
warnings++;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// --- Summary ---
|
|
514
|
+
console.log("\n ────────────────────────────────────");
|
|
515
|
+
console.log(` Results: ${passed} passed, ${warnings} warnings, ${issues} issues`);
|
|
516
|
+
console.log(" ────────────────────────────────────");
|
|
517
|
+
|
|
518
|
+
if (issues > 0) {
|
|
519
|
+
console.log(`\n ${issues} issue(s) found. Fix these before shipping.`);
|
|
520
|
+
console.log(" Run: npx secure-repo init (adds missing policy files)");
|
|
521
|
+
} else if (warnings > 0) {
|
|
522
|
+
console.log("\n No critical issues. Some improvements recommended.");
|
|
523
|
+
} else {
|
|
524
|
+
console.log("\n Looking good! Your repo meets basic security standards.");
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
console.log();
|
|
528
|
+
return issues;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// ============================================================
|
|
532
|
+
// CHECK — compare local templates against latest version
|
|
533
|
+
// ============================================================
|
|
534
|
+
function check() {
|
|
535
|
+
const destDir = process.cwd();
|
|
536
|
+
|
|
537
|
+
console.log("\n Checking installed templates...\n");
|
|
538
|
+
|
|
539
|
+
if (!fs.existsSync(FREE_DIR)) {
|
|
540
|
+
console.log(" Could not find template source files.\n");
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
fs.readdirSync(FREE_DIR).forEach((file) => {
|
|
545
|
+
const localPath = path.join(destDir, file);
|
|
546
|
+
if (!fs.existsSync(localPath)) {
|
|
547
|
+
console.log(` [missing] ${file}`);
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
const local = fs.readFileSync(localPath, "utf8");
|
|
551
|
+
const latest = fs.readFileSync(path.join(FREE_DIR, file), "utf8");
|
|
552
|
+
if (local === latest) {
|
|
553
|
+
console.log(` [current] ${file}`);
|
|
554
|
+
} else {
|
|
555
|
+
console.log(` [outdated] ${file} — run init --force to update`);
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
console.log();
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// ============================================================
|
|
563
|
+
// Main
|
|
564
|
+
// ============================================================
|
|
565
|
+
switch (command) {
|
|
566
|
+
case "init":
|
|
567
|
+
init();
|
|
568
|
+
break;
|
|
569
|
+
case "audit":
|
|
570
|
+
audit();
|
|
571
|
+
break;
|
|
572
|
+
case "import":
|
|
573
|
+
importPack();
|
|
574
|
+
break;
|
|
575
|
+
case "list":
|
|
576
|
+
listTemplates();
|
|
577
|
+
break;
|
|
578
|
+
case "check":
|
|
579
|
+
check();
|
|
580
|
+
break;
|
|
581
|
+
case "help":
|
|
582
|
+
case "--help":
|
|
583
|
+
case "-h":
|
|
584
|
+
printHelp();
|
|
585
|
+
break;
|
|
586
|
+
default:
|
|
587
|
+
printHelp();
|
|
588
|
+
break;
|
|
589
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "secure-repo",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Drop production-grade security standards into any repo. Audit your repo for security issues. Templates for AI-assisted development.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"secure-repo": "./bin/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"keywords": [
|
|
9
|
+
"security",
|
|
10
|
+
"audit",
|
|
11
|
+
"templates",
|
|
12
|
+
"saas",
|
|
13
|
+
"nextjs",
|
|
14
|
+
"supabase",
|
|
15
|
+
"firebase",
|
|
16
|
+
"production",
|
|
17
|
+
"standards",
|
|
18
|
+
"authentication",
|
|
19
|
+
"api-security",
|
|
20
|
+
"security-audit",
|
|
21
|
+
"checklist",
|
|
22
|
+
"devtools",
|
|
23
|
+
"ai-coding"
|
|
24
|
+
],
|
|
25
|
+
"author": "sebiomoa",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"files": [
|
|
28
|
+
"bin/",
|
|
29
|
+
"templates/free/"
|
|
30
|
+
],
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=16"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# API Standards
|
|
2
|
+
|
|
3
|
+
All API endpoints in this repository must follow these rules. Insecure or unvalidated endpoints are the most common attack vector in modern web apps.
|
|
4
|
+
|
|
5
|
+
## Rules (MUST FOLLOW)
|
|
6
|
+
|
|
7
|
+
- **Validate all input**
|
|
8
|
+
- Every endpoint must validate request body, query params, and path params.
|
|
9
|
+
- Use a schema validation library (e.g. zod, yup, joi).
|
|
10
|
+
- Reject invalid payloads with 400 and a safe error message.
|
|
11
|
+
- Never pass raw user input to database queries or system commands.
|
|
12
|
+
|
|
13
|
+
- **Rate limit all public endpoints**
|
|
14
|
+
- Login, signup, password reset: strict limits (5-10 req/min per IP).
|
|
15
|
+
- Public read endpoints: moderate limits (60-120 req/min per IP).
|
|
16
|
+
- Authenticated endpoints: per-user limits where appropriate.
|
|
17
|
+
- Return 429 when limits are exceeded.
|
|
18
|
+
|
|
19
|
+
- **Authenticate before processing**
|
|
20
|
+
- Protected endpoints must verify auth before any business logic runs.
|
|
21
|
+
- Check auth first, validate input second, then process.
|
|
22
|
+
- Return 401 for missing auth, 403 for insufficient permissions.
|
|
23
|
+
|
|
24
|
+
- **Never expose internal errors**
|
|
25
|
+
- Return generic error messages to clients (e.g. "Something went wrong").
|
|
26
|
+
- Log full error details server-side only.
|
|
27
|
+
- Never leak stack traces, SQL errors, or internal paths in responses.
|
|
28
|
+
|
|
29
|
+
- **Use consistent error format**
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"error": {
|
|
33
|
+
"code": "VALIDATION_ERROR",
|
|
34
|
+
"message": "Email is required."
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
- **Idempotency for write operations**
|
|
40
|
+
- POST endpoints that create resources should support idempotency keys where possible.
|
|
41
|
+
- PUT and DELETE must be idempotent by design.
|
|
42
|
+
|
|
43
|
+
## Endpoint Conventions
|
|
44
|
+
|
|
45
|
+
### Naming
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
GET /api/resources -> list
|
|
49
|
+
GET /api/resources/:id -> get one
|
|
50
|
+
POST /api/resources -> create
|
|
51
|
+
PUT /api/resources/:id -> update (full replace)
|
|
52
|
+
PATCH /api/resources/:id -> update (partial)
|
|
53
|
+
DELETE /api/resources/:id -> delete
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Response codes
|
|
57
|
+
|
|
58
|
+
| Code | Use |
|
|
59
|
+
|------|-----|
|
|
60
|
+
| 200 | Successful read or update |
|
|
61
|
+
| 201 | Successful creation |
|
|
62
|
+
| 204 | Successful deletion (no body) |
|
|
63
|
+
| 400 | Invalid input |
|
|
64
|
+
| 401 | Not authenticated |
|
|
65
|
+
| 403 | Not authorized |
|
|
66
|
+
| 404 | Resource not found |
|
|
67
|
+
| 409 | Conflict (duplicate, version mismatch) |
|
|
68
|
+
| 429 | Rate limited |
|
|
69
|
+
| 500 | Server error (never expose details) |
|
|
70
|
+
|
|
71
|
+
## Request/Response Rules
|
|
72
|
+
|
|
73
|
+
- **Content-Type**: Always set and check `Content-Type: application/json` for JSON endpoints.
|
|
74
|
+
- **CORS**: Only allow specific origins. Never use `*` on authenticated endpoints.
|
|
75
|
+
- **Pagination**: List endpoints must support pagination. Default limit: 20-50. Max limit: 100.
|
|
76
|
+
- **Filtering**: Validate filter params against an allowlist. Never pass raw filter values to queries.
|
|
77
|
+
|
|
78
|
+
## Server Route Pattern (Next.js)
|
|
79
|
+
|
|
80
|
+
Every API route should follow this structure:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
export async function POST(request: Request) {
|
|
84
|
+
// 1. Authenticate
|
|
85
|
+
const session = await verifySession(request);
|
|
86
|
+
if (!session) return Response.json({ error: { code: "UNAUTHORIZED" } }, { status: 401 });
|
|
87
|
+
|
|
88
|
+
// 2. Validate input
|
|
89
|
+
const body = await request.json();
|
|
90
|
+
const parsed = schema.safeParse(body);
|
|
91
|
+
if (!parsed.success) return Response.json({ error: { code: "VALIDATION_ERROR" } }, { status: 400 });
|
|
92
|
+
|
|
93
|
+
// 3. Process
|
|
94
|
+
const result = await doTheThing(parsed.data);
|
|
95
|
+
|
|
96
|
+
// 4. Respond
|
|
97
|
+
return Response.json(result, { status: 200 });
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Required Checks Before Merge
|
|
102
|
+
|
|
103
|
+
Before merging any API changes:
|
|
104
|
+
|
|
105
|
+
- [ ] All inputs are validated with a schema
|
|
106
|
+
- [ ] Rate limiting is configured for public endpoints
|
|
107
|
+
- [ ] Auth is checked before business logic
|
|
108
|
+
- [ ] Error responses do not leak internal details
|
|
109
|
+
- [ ] CORS is configured for specific origins only
|
|
110
|
+
- [ ] New endpoints are documented
|
|
111
|
+
- [ ] No raw user input reaches database queries or shell commands
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Authentication & Authorization Policy
|
|
2
|
+
|
|
3
|
+
This repository handles authenticated user sessions and role-based access. Authentication mistakes can expose user data or privileged operations.
|
|
4
|
+
|
|
5
|
+
Treat all authentication logic as **security-critical**.
|
|
6
|
+
|
|
7
|
+
## Rules (MUST FOLLOW)
|
|
8
|
+
|
|
9
|
+
- **Never trust client authentication state**
|
|
10
|
+
- The server must always verify tokens or sessions.
|
|
11
|
+
- Client-side auth state is for UI rendering only.
|
|
12
|
+
|
|
13
|
+
- **JWT tokens must be verified server-side**
|
|
14
|
+
- Verify signature against your secret/public key.
|
|
15
|
+
- Verify expiration (`exp` claim).
|
|
16
|
+
- Verify issuer (`iss`) and audience (`aud`) if applicable.
|
|
17
|
+
- Reject tokens that fail any check.
|
|
18
|
+
|
|
19
|
+
- **Use short-lived access tokens**
|
|
20
|
+
- Access tokens: 15 minutes or less.
|
|
21
|
+
- Refresh tokens: rotate on every use, invalidate old ones.
|
|
22
|
+
- Never reuse refresh tokens.
|
|
23
|
+
|
|
24
|
+
- **Do not store tokens in localStorage**
|
|
25
|
+
- Prefer `httpOnly`, `Secure`, `SameSite=Strict` cookies.
|
|
26
|
+
- If localStorage is unavoidable, document the risk and compensate with short TTLs.
|
|
27
|
+
|
|
28
|
+
- **Session revocation must be supported**
|
|
29
|
+
- Users must be able to log out of all devices.
|
|
30
|
+
- Token revocation or session invalidation must be server-enforced.
|
|
31
|
+
|
|
32
|
+
- **Password rules**
|
|
33
|
+
- Minimum 8 characters. No maximum below 128.
|
|
34
|
+
- Use bcrypt, scrypt, or argon2 for hashing. Never SHA-256 or MD5 for passwords.
|
|
35
|
+
- Enforce rate limiting on login attempts (max 5 per minute per IP/account).
|
|
36
|
+
|
|
37
|
+
- **Magic link / OTP rules**
|
|
38
|
+
- Links and codes expire within 10 minutes.
|
|
39
|
+
- Single use only. Invalidate after first use.
|
|
40
|
+
- Rate limit generation (max 3 per 15 minutes per email).
|
|
41
|
+
|
|
42
|
+
## Authorization Model
|
|
43
|
+
|
|
44
|
+
Use role-based access control (RBAC).
|
|
45
|
+
|
|
46
|
+
### Default roles:
|
|
47
|
+
|
|
48
|
+
| Role | Description |
|
|
49
|
+
|------|-------------|
|
|
50
|
+
| `anonymous` | Unauthenticated visitor. Read-only public data. |
|
|
51
|
+
| `authenticated` | Logged-in user. Access own data only. |
|
|
52
|
+
| `admin` | Full access. Verified server-side. |
|
|
53
|
+
|
|
54
|
+
### Rules:
|
|
55
|
+
|
|
56
|
+
- Admin actions must require server-side role verification.
|
|
57
|
+
- Never trust role claims sent from the client.
|
|
58
|
+
- Roles must be validated against database state, not just JWT claims.
|
|
59
|
+
- Role escalation (user -> admin) must require re-authentication.
|
|
60
|
+
|
|
61
|
+
## Protected Endpoints
|
|
62
|
+
|
|
63
|
+
All endpoints requiring authentication must:
|
|
64
|
+
|
|
65
|
+
1. Verify session or JWT before processing.
|
|
66
|
+
2. Validate request input with a schema (e.g. zod).
|
|
67
|
+
3. Enforce role-based permissions.
|
|
68
|
+
4. Return 401 for missing auth, 403 for insufficient permissions.
|
|
69
|
+
|
|
70
|
+
### Endpoint patterns:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
/api/user/** -> requires authenticated role
|
|
74
|
+
/api/account/** -> requires authenticated role + ownership check
|
|
75
|
+
/api/admin/** -> requires admin role (verified server-side)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Common Auth Vulnerabilities To Avoid
|
|
79
|
+
|
|
80
|
+
- Accepting expired or malformed tokens
|
|
81
|
+
- Storing tokens in unsafe storage (localStorage, query params, cookies without httpOnly)
|
|
82
|
+
- Trusting client-provided role claims
|
|
83
|
+
- Allowing unauthenticated password reset flows
|
|
84
|
+
- Missing brute-force protections on login
|
|
85
|
+
- Not invalidating sessions on password change
|
|
86
|
+
- Open redirect after login (validate redirect URLs server-side)
|
|
87
|
+
- CSRF on state-changing auth endpoints (use SameSite cookies or CSRF tokens)
|
|
88
|
+
|
|
89
|
+
## Required Checks Before Merge
|
|
90
|
+
|
|
91
|
+
Before merging any authentication-related changes:
|
|
92
|
+
|
|
93
|
+
- [ ] Session/token verification works correctly server-side
|
|
94
|
+
- [ ] Protected endpoints reject anonymous users with 401
|
|
95
|
+
- [ ] Admin routes require and verify admin role server-side
|
|
96
|
+
- [ ] Tokens expire correctly and cannot be reused after expiration
|
|
97
|
+
- [ ] Password changes invalidate existing sessions
|
|
98
|
+
- [ ] Rate limiting is active on login and token generation endpoints
|
|
99
|
+
- [ ] No auth tokens appear in URLs, logs, or error messages
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
This repository handles production user data and privileged database access. Treat security requirements as **non-optional**.
|
|
4
|
+
|
|
5
|
+
## Agent Rules (MUST FOLLOW)
|
|
6
|
+
|
|
7
|
+
- **No secrets in code**
|
|
8
|
+
- Never commit: Database passwords, JWT secrets, API keys, service account keys, or admin credentials.
|
|
9
|
+
- Client-side env vars must be limited to public-safe values only (e.g. public API URLs, publishable keys).
|
|
10
|
+
|
|
11
|
+
- **Assume public keys are public**
|
|
12
|
+
- Anyone can extract client-side keys from a web app.
|
|
13
|
+
- All data protection must be enforced via database access control, safe RPCs, and server-side checks.
|
|
14
|
+
|
|
15
|
+
- **Privileged key usage**
|
|
16
|
+
- Admin or service-level keys may only be used in:
|
|
17
|
+
- Server-side route handlers (e.g. Next.js `src/app/api/**`, Express routes)
|
|
18
|
+
- Serverless functions or edge functions
|
|
19
|
+
- Trusted backend workers
|
|
20
|
+
- Never expose privileged keys to the browser.
|
|
21
|
+
|
|
22
|
+
- **Database access control is mandatory**
|
|
23
|
+
- Any table with user data must have access control (row-level security, middleware checks, or ORM-level policies).
|
|
24
|
+
- Avoid permissive write rules that allow any user to insert or modify any row.
|
|
25
|
+
|
|
26
|
+
- **Prefer server endpoints for public write actions**
|
|
27
|
+
- Any endpoint that records data (analytics, clicks, form submissions) must:
|
|
28
|
+
- Validate input (e.g. zod)
|
|
29
|
+
- Apply rate limits
|
|
30
|
+
- Use server-side database writes with privileged credentials
|
|
31
|
+
- Client components must not write to the database directly. Use a server route.
|
|
32
|
+
|
|
33
|
+
- **Function and procedure hardening**
|
|
34
|
+
- Database functions that run with elevated privileges must:
|
|
35
|
+
- Have a fixed, safe execution context.
|
|
36
|
+
- Restrict execution to the roles that need it.
|
|
37
|
+
|
|
38
|
+
- **Do not increase attack surface**
|
|
39
|
+
- Do not attach privileged objects to `window` or global scope unless strictly required.
|
|
40
|
+
- Avoid broad CORS (`*`) except where explicitly intended and reviewed.
|
|
41
|
+
|
|
42
|
+
## Required Checks Before Merge
|
|
43
|
+
|
|
44
|
+
- **Repository scan**
|
|
45
|
+
- Search for leaked keys:
|
|
46
|
+
- Database passwords or connection strings
|
|
47
|
+
- JWTs (`eyJ...`)
|
|
48
|
+
- Any secret keys, tokens, or credentials
|
|
49
|
+
|
|
50
|
+
- **Database review** (required after any DB/access control change)
|
|
51
|
+
- Verify access control policies cover new tables and columns.
|
|
52
|
+
- Run security and performance checks available in your database platform.
|
|
53
|
+
|
|
54
|
+
- **Verify public write paths**
|
|
55
|
+
- Confirm no tables allow anonymous inserts without validation/rate limiting.
|
|
56
|
+
- Confirm any public write action is routed through a server endpoint.
|
|
57
|
+
|
|
58
|
+
## Patterns To Use
|
|
59
|
+
|
|
60
|
+
- **Server-side event tracking**
|
|
61
|
+
- Use a server API route with rate limiting and privileged database writes.
|
|
62
|
+
- Example endpoints:
|
|
63
|
+
- `/api/track-click` -> records event via server-side DB client
|
|
64
|
+
- `/api/track-event` -> records event via server-side DB client
|
|
65
|
+
|
|
66
|
+
- **Restrict direct database function execution**
|
|
67
|
+
- Sensitive database functions must not be callable by anonymous or public roles.
|
|
68
|
+
- Execution should be restricted to server-side service accounts.
|
|
69
|
+
|
|
70
|
+
- **Public analytics**
|
|
71
|
+
- Prefer a server endpoint that accepts sanitized payloads and enforces bot checks.
|
|
72
|
+
|
|
73
|
+
- **Admin-only actions**
|
|
74
|
+
- Admin endpoints must use privileged database credentials and proper authorization.
|
|
75
|
+
- Avoid using public/anonymous database clients inside admin routes.
|
|
76
|
+
|
|
77
|
+
## Enforced Architecture
|
|
78
|
+
|
|
79
|
+
### Option A: Server routes for any privileged writes
|
|
80
|
+
|
|
81
|
+
- Direct writes to protected tables from client code are not allowed.
|
|
82
|
+
- All privileged writes must go through server-side route handlers.
|
|
83
|
+
- Server routes must:
|
|
84
|
+
- Validate input (e.g. zod)
|
|
85
|
+
- Rate limit
|
|
86
|
+
- Authenticate via `Authorization: Bearer <JWT>` or session cookie
|
|
87
|
+
- Use privileged credentials for DB writes
|
|
88
|
+
|
|
89
|
+
### Option B: Anonymous activity is local-only
|
|
90
|
+
|
|
91
|
+
- Anonymous activity must remain local until login.
|
|
92
|
+
- Cloud state for user data is **authenticated-only**:
|
|
93
|
+
- User lists and collections
|
|
94
|
+
- Recently viewed items
|
|
95
|
+
- Progress tracking
|
|
96
|
+
- Any user-specific data
|
|
97
|
+
|
|
98
|
+
## Database Hardening Rules
|
|
99
|
+
|
|
100
|
+
- **Protected tables**
|
|
101
|
+
- Admin tables, user tokens, user data tables, and any sensitive business data.
|
|
102
|
+
|
|
103
|
+
- **Access control and grants**
|
|
104
|
+
- Admin tables are accessible only via privileged server-side credentials.
|
|
105
|
+
- Public tables allow read-only access where appropriate.
|
|
106
|
+
- Table writes are server-side only (via server endpoints).
|
|
107
|
+
|
|
108
|
+
- **Function grants + hardening**
|
|
109
|
+
- Sensitive database functions are restricted to privileged roles only.
|
|
110
|
+
- Functions with elevated privileges must have a fixed, safe execution context.
|
|
111
|
+
|
|
112
|
+
## Incident Response (If a key is exposed)
|
|
113
|
+
|
|
114
|
+
1. Rotate impacted keys immediately.
|
|
115
|
+
2. Audit access control policies and function grants.
|
|
116
|
+
3. Review logs for unusual access patterns.
|
|
117
|
+
4. Add a regression test/checklist entry to prevent recurrence.
|