secure-repo 1.2.0 → 1.3.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 +5 -3
- package/bin/cli.js +95 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -93,7 +93,7 @@ Every template is:
|
|
|
93
93
|
|
|
94
94
|
## What You Get (Free)
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
Four templates that cover the foundations:
|
|
97
97
|
|
|
98
98
|
**SECURITY.md** — No secrets in code. Privileged keys server-side only. Database access control mandatory. Server endpoints for all writes. Incident response steps.
|
|
99
99
|
|
|
@@ -101,6 +101,8 @@ Three templates that cover the foundations:
|
|
|
101
101
|
|
|
102
102
|
**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.
|
|
103
103
|
|
|
104
|
+
**ACCESSIBILITY.md** — WCAG 2.1 AA compliance. Semantic HTML. Keyboard navigation. Screen reader support. Color contrast. Font sizes. Touch targets.
|
|
105
|
+
|
|
104
106
|
Each file includes:
|
|
105
107
|
- Rules marked "MUST FOLLOW"
|
|
106
108
|
- Code patterns to copy
|
|
@@ -181,12 +183,12 @@ npx secure-repo list # Show all available templates
|
|
|
181
183
|
| | Free | Pro Pack |
|
|
182
184
|
|--|------|---------|
|
|
183
185
|
| Security audit command | Included | Included |
|
|
184
|
-
| Core security policies (
|
|
186
|
+
| Core security policies (4 files) | Included | Included |
|
|
185
187
|
| Deep engineering standards (18 files) | - | Included |
|
|
186
188
|
| 100+ point audit checklist | - | Included |
|
|
187
189
|
| Stack presets (Supabase, Firebase) | - | Included |
|
|
188
190
|
| Code examples (5 files) | - | Included |
|
|
189
|
-
| **Total policy files** | **
|
|
191
|
+
| **Total policy files** | **4** | **31** |
|
|
190
192
|
|
|
191
193
|
---
|
|
192
194
|
|
package/bin/cli.js
CHANGED
|
@@ -213,6 +213,31 @@ function downloadProZip(destPath, licenseKey) {
|
|
|
213
213
|
});
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
+
// ============================================================
|
|
217
|
+
// Detect stack from package.json dependencies
|
|
218
|
+
// ============================================================
|
|
219
|
+
function detectStacks(projectDir) {
|
|
220
|
+
const pkgPath = path.join(projectDir, "package.json");
|
|
221
|
+
if (!fs.existsSync(pkgPath)) return [];
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
225
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
226
|
+
const stacks = [];
|
|
227
|
+
|
|
228
|
+
if (allDeps["@supabase/supabase-js"] || allDeps["@supabase/ssr"] || allDeps["@supabase/auth-helpers-nextjs"]) {
|
|
229
|
+
stacks.push("supabase");
|
|
230
|
+
}
|
|
231
|
+
if (allDeps["firebase"] || allDeps["firebase-admin"] || allDeps["firebase-functions"]) {
|
|
232
|
+
stacks.push("firebase");
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return stacks;
|
|
236
|
+
} catch {
|
|
237
|
+
return [];
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
216
241
|
// ============================================================
|
|
217
242
|
// Extract zip and install pro templates
|
|
218
243
|
// ============================================================
|
|
@@ -244,13 +269,18 @@ function installFromZip(zipPath, outputDir, force) {
|
|
|
244
269
|
totalSkipped += r.skipped;
|
|
245
270
|
}
|
|
246
271
|
|
|
247
|
-
// Copy presets
|
|
272
|
+
// Copy presets — only install presets matching detected stack
|
|
248
273
|
const presetsDir = path.join(tempDir, "presets");
|
|
249
274
|
if (fs.existsSync(presetsDir)) {
|
|
275
|
+
const detectedStacks = detectStacks(outputDir);
|
|
250
276
|
const presets = fs.readdirSync(presetsDir).filter((f) =>
|
|
251
277
|
fs.statSync(path.join(presetsDir, f)).isDirectory()
|
|
252
278
|
);
|
|
253
279
|
presets.forEach((preset) => {
|
|
280
|
+
if (detectedStacks.length > 0 && !detectedStacks.includes(preset)) {
|
|
281
|
+
console.log(`\n Preset: ${preset} (skipped — not detected in package.json)`);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
254
284
|
const presetDest = path.join(outputDir, `${preset}-preset`);
|
|
255
285
|
console.log(`\n Preset: ${preset} (-> ${preset}-preset/)`);
|
|
256
286
|
const r = copyFiles(path.join(presetsDir, preset), presetDest, force);
|
|
@@ -344,9 +374,20 @@ function writeAgentFiles(outputDir, force) {
|
|
|
344
374
|
const dir = path.dirname(fullPath);
|
|
345
375
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
346
376
|
|
|
347
|
-
if (fs.existsSync(fullPath)
|
|
348
|
-
|
|
349
|
-
|
|
377
|
+
if (fs.existsSync(fullPath)) {
|
|
378
|
+
const existing = fs.readFileSync(fullPath, "utf8");
|
|
379
|
+
const isShipSecureBoilerplate = existing.includes("# Security Policies — MUST READ") && existing.includes("ShipSecure");
|
|
380
|
+
if (isShipSecureBoilerplate && force) {
|
|
381
|
+
fs.writeFileSync(fullPath, AGENT_INSTRUCTION);
|
|
382
|
+
console.log(` [done] ${filePath} (${name})`);
|
|
383
|
+
written++;
|
|
384
|
+
} else if (isShipSecureBoilerplate) {
|
|
385
|
+
console.log(` [skip] ${filePath} (${name}) — use --force to overwrite`);
|
|
386
|
+
skipped++;
|
|
387
|
+
} else {
|
|
388
|
+
console.log(` [skip] ${filePath} (${name}) — existing content detected, won't overwrite`);
|
|
389
|
+
skipped++;
|
|
390
|
+
}
|
|
350
391
|
} else {
|
|
351
392
|
fs.writeFileSync(fullPath, AGENT_INSTRUCTION);
|
|
352
393
|
console.log(` [done] ${filePath} (${name})`);
|
|
@@ -482,6 +523,29 @@ function importPack() {
|
|
|
482
523
|
// ============================================================
|
|
483
524
|
// AUDIT — scan repo for security issues (the viral command)
|
|
484
525
|
// ============================================================
|
|
526
|
+
function checkForUpdate() {
|
|
527
|
+
return new Promise((resolve) => {
|
|
528
|
+
const timeout = setTimeout(() => resolve(null), 3000);
|
|
529
|
+
https.get("https://registry.npmjs.org/secure-repo/latest", (res) => {
|
|
530
|
+
let data = "";
|
|
531
|
+
res.on("data", (chunk) => (data += chunk));
|
|
532
|
+
res.on("end", () => {
|
|
533
|
+
clearTimeout(timeout);
|
|
534
|
+
try {
|
|
535
|
+
const latest = JSON.parse(data).version;
|
|
536
|
+
const pkg = require("../package.json");
|
|
537
|
+
resolve(latest !== pkg.version ? latest : null);
|
|
538
|
+
} catch {
|
|
539
|
+
resolve(null);
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
}).on("error", () => {
|
|
543
|
+
clearTimeout(timeout);
|
|
544
|
+
resolve(null);
|
|
545
|
+
});
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
|
|
485
549
|
function audit() {
|
|
486
550
|
const targetDir = getArg("--output") || process.cwd();
|
|
487
551
|
|
|
@@ -496,7 +560,7 @@ function audit() {
|
|
|
496
560
|
// Score weights (total = 100)
|
|
497
561
|
const SCORE_WEIGHTS = {
|
|
498
562
|
policyHigh: 10, // 4 high-severity files × 10 = 40
|
|
499
|
-
policyMedium: 5, //
|
|
563
|
+
policyMedium: 5, // 4 medium-severity files × 5 = 20
|
|
500
564
|
gitignoreEnv: 10, // .env in .gitignore
|
|
501
565
|
noEnvFiles: 10, // no committed .env files
|
|
502
566
|
envExample: 5, // .env.example exists
|
|
@@ -546,9 +610,22 @@ function audit() {
|
|
|
546
610
|
envFiles.forEach((envFile) => {
|
|
547
611
|
const envPath = path.join(targetDir, envFile);
|
|
548
612
|
if (fs.existsSync(envPath)) {
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
613
|
+
// Check if file is git-ignored before flagging
|
|
614
|
+
let isIgnored = false;
|
|
615
|
+
try {
|
|
616
|
+
execSync(`git check-ignore -q "${envPath}"`, { stdio: "pipe" });
|
|
617
|
+
isIgnored = true;
|
|
618
|
+
} catch {
|
|
619
|
+
// exit code 1 = not ignored
|
|
620
|
+
}
|
|
621
|
+
if (isIgnored) {
|
|
622
|
+
console.log(` [pass] ${envFile} exists but is gitignored`);
|
|
623
|
+
passed++;
|
|
624
|
+
} else {
|
|
625
|
+
console.log(` [FAIL] ${envFile} exists and is NOT gitignored — may contain secrets`);
|
|
626
|
+
issues++;
|
|
627
|
+
envFilesFound++;
|
|
628
|
+
}
|
|
552
629
|
}
|
|
553
630
|
});
|
|
554
631
|
if (envFilesFound === 0) {
|
|
@@ -664,7 +741,16 @@ function audit() {
|
|
|
664
741
|
console.log(" Run: npx secure-repo upgrade");
|
|
665
742
|
}
|
|
666
743
|
|
|
667
|
-
|
|
744
|
+
// Check for newer version (non-blocking)
|
|
745
|
+
checkForUpdate().then((latest) => {
|
|
746
|
+
if (latest) {
|
|
747
|
+
console.log(`\n Update available: v${latest} (you have v${require("../package.json").version})`);
|
|
748
|
+
console.log(" Run: npx secure-repo@latest init\n");
|
|
749
|
+
} else {
|
|
750
|
+
console.log();
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
|
|
668
754
|
return issues;
|
|
669
755
|
}
|
|
670
756
|
|
package/package.json
CHANGED