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.
Files changed (3) hide show
  1. package/README.md +5 -3
  2. package/bin/cli.js +95 -9
  3. 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
- Three templates that cover the foundations:
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 (3 files) | Included | Included |
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** | **3** | **30** |
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) && !force) {
348
- console.log(` [skip] ${filePath} (${name}) — use --force to overwrite`);
349
- skipped++;
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, // 3 medium-severity files × 5 = 15
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
- console.log(` [FAIL] ${envFile} exists in repo may contain secrets`);
550
- issues++;
551
- envFilesFound++;
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
- console.log();
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "secure-repo",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Drop production-grade security standards into any repo. Audit your repo for security issues. Templates for AI-assisted development.",
5
5
  "bin": {
6
6
  "secure-repo": "./bin/cli.js"