create-daloy 0.1.20 → 0.1.22

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 CHANGED
@@ -18,6 +18,7 @@ The CLI is interactive when arguments are missing. It will ask you for:
18
18
  `deno-basic` runtime template
19
19
  - Whether to install dependencies
20
20
  - Whether to initialize a git repository
21
+ - Whether to add hardened GitHub Actions and security/governance files
21
22
 
22
23
  Interactive runs use a polished terminal UI with a DaloyJS welcome banner,
23
24
  arrow-key template and package-manager pickers, progress indicators, and a
@@ -30,6 +31,8 @@ script-friendly transcript with the same decisions and next steps.
30
31
  pnpm create daloy@latest my-api \
31
32
  --template node-basic \
32
33
  --package-manager pnpm \
34
+ --with-ci \
35
+ --code-owner @acme/security \
33
36
  --install \
34
37
  --git
35
38
  ```
@@ -44,6 +47,8 @@ pnpm create daloy@latest my-api \
44
47
  | `--install` / `--no-install` | Install dependencies after scaffolding. Defaults to interactive. |
45
48
  | `--git` / `--no-git` | Initialize a git repository. Defaults to interactive. |
46
49
  | `--minimal` | Strip the bookstore demo route and the built-in `/docs` + `/openapi.json` routes so only the framework bootstrap and `/healthz` ship. |
50
+ | `--with-ci` / `--no-ci` | Add the hardened GitHub Actions, Dependabot, CODEOWNERS, SECURITY.md, and lockfile-source verification bundle. |
51
+ | `--code-owner <owner>` | Replace the CODEOWNERS placeholder when `--with-ci` is used, for example `@acme/security`. |
47
52
  | `--force` | Overwrite an existing non-empty directory. |
48
53
  | `--yes` | Accept all defaults; never prompt. |
49
54
  | `--help` | Print usage and exit. |
@@ -118,14 +123,50 @@ Sentinel comments (`// daloy-minimal:strip-start <tag>` /
118
123
  `// daloy-minimal:strip-end <tag>`) survive a default scaffold so you can
119
124
  re-run with `--minimal`, or delete the marked blocks by hand later.
120
125
 
126
+ ## Hardened GitHub security bundle
127
+
128
+ Pass `--with-ci` when you want the generated project to start with the same
129
+ security posture as a serious company repo:
130
+
131
+ ```bash
132
+ pnpm create daloy@latest my-api \
133
+ --template node-basic \
134
+ --package-manager pnpm \
135
+ --with-ci \
136
+ --code-owner @acme/security
137
+ ```
138
+
139
+ For Node-style templates, the bundle adds:
140
+
141
+ - `.github/workflows/ci.yml` with top-level `permissions: {}`, pinned actions,
142
+ `harden-runner`, `persist-credentials: false`, no package-manager cache, and
143
+ install scripts disabled.
144
+ - `.github/workflows/release.yml` as a disabled-by-default npm trusted publishing
145
+ skeleton. It only publishes when `NPM_PUBLISH_ENABLED=true`, the package is no
146
+ longer private, and the protected `npm-publish` environment is configured.
147
+ - CodeQL, OpenSSF Scorecard, zizmor, Dependabot, CODEOWNERS, and `SECURITY.md`.
148
+ - `scripts/verify-lockfile-sources.mjs` plus a `verify:lockfile` package script
149
+ that rejects git dependencies and non-registry tarball URLs in text lockfiles.
150
+
151
+ For `deno-basic`, `--with-ci` generates a Deno-native CI workflow plus CodeQL,
152
+ Scorecard, zizmor, Dependabot for GitHub Actions, CODEOWNERS, and `SECURITY.md`.
153
+ It does not generate an npm release workflow because the Deno template has no
154
+ `package.json`.
155
+
156
+ If you omit `--code-owner`, the generated CODEOWNERS file uses
157
+ `@your-org/security-team` as a placeholder. Replace it before relying on branch
158
+ protection. You should also enable GitHub secret scanning, push protection, and
159
+ required status checks in the repository settings.
160
+
121
161
  ## What the CLI guarantees
122
162
 
123
163
  - Zero runtime dependencies (uses only Node built-ins) for a clean supply-chain footprint.
124
164
  - A modern terminal experience with Unicode/color capability detection and ASCII fallbacks.
125
165
  - Templates are copied verbatim from this package's `templates/` directory.
126
- - Files and folders prefixed with `_` are renamed on copy (`_gitignore` → `.gitignore`, `_npmrc` → `.npmrc`, `_agents/` → `.agents/`) to survive npm packing.
166
+ - Files and folders prefixed with `_` are renamed on copy (`_gitignore` → `.gitignore`, `_npmrc` → `.npmrc`, `_github/` → `.github`, `_agents/` → `.agents/`) to survive npm packing.
127
167
  - pnpm-specific `.npmrc` hardening is kept only when you choose `pnpm`; other package managers get a clean project without unsupported config warnings.
128
168
  - pnpm projects ship with `ignore-scripts=true`, `minimum-release-age=1440`, `verify-store-integrity=true`, `prefer-frozen-lockfile=true`, and `strict-peer-dependencies=true` by default.
169
+ - `--with-ci` projects ship with pinned GitHub Actions workflows, CODEOWNERS, Dependabot, SECURITY.md, and lockfile-source verification.
129
170
  - The CLI never executes template scripts and never makes network calls beyond the package manager you select.
130
171
 
131
172
  ## AI agent helper files
@@ -13,6 +13,7 @@ import { fileURLToPath } from "node:url";
13
13
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
14
  const PKG_ROOT = path.resolve(__dirname, "..");
15
15
  const TEMPLATES_DIR = path.join(PKG_ROOT, "templates");
16
+ const CI_TEMPLATES_DIR = path.join(TEMPLATES_DIR, "_ci");
16
17
 
17
18
  const TEMPLATE_OPTIONS = [
18
19
  {
@@ -56,6 +57,7 @@ const RENAME_ON_COPY = new Map([
56
57
  ["_gitignore", ".gitignore"],
57
58
  ["_npmrc", ".npmrc"],
58
59
  ["_env.example", ".env.example"],
60
+ ["_github", ".github"],
59
61
  // Directory: holds skill files for AI coding agents under
60
62
  // `.agents/skills/<skill-name>/SKILL.md`. Templates author this as
61
63
  // `_agents/` so npm pack does not drop the dotfolder during publish.
@@ -72,6 +74,8 @@ const NO_PACKAGE_JSON_TEMPLATES = new Set(["deno-basic"]);
72
74
  // `daloy-minimal:strip-start <tag>` / `daloy-minimal:strip-end <tag>`
73
75
  // sentinels.
74
76
  const MINIMAL_STRIP_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".mjs", ".cjs", ".md"]);
77
+ const CI_PLACEHOLDER_EXTENSIONS = new Set([".json", ".md", ".mjs", ".yaml", ".yml"]);
78
+ const CI_PLACEHOLDER_FILES = new Set(["CODEOWNERS"]);
75
79
 
76
80
  // ----------------------------------------------------------------------------
77
81
  // Terminal capability detection + style primitives.
@@ -308,6 +312,8 @@ ${heading("Options")}
308
312
  ${color(COLORS.green, "--install / --no-install")} Install dependencies after scaffolding.
309
313
  ${color(COLORS.green, "--git / --no-git")} Initialize a git repository.
310
314
  ${color(COLORS.green, "--minimal")} Strip the bookstore + Swagger/OpenAPI demo routes.
315
+ ${color(COLORS.green, "--with-ci / --no-ci")} Add hardened GitHub Actions + governance files.
316
+ ${color(COLORS.green, "--code-owner <owner>")} CODEOWNERS owner for --with-ci, e.g. @acme/security.
311
317
  ${color(COLORS.green, "--force")} Overwrite an existing non-empty directory.
312
318
  ${color(COLORS.green, "--yes, -y")} Accept all defaults; never prompt.
313
319
  ${color(COLORS.green, "--help, -h")} Print this help.
@@ -353,6 +359,8 @@ function parseArgs(argv) {
353
359
  version: false,
354
360
  listTemplates: false,
355
361
  minimal: false,
362
+ ci: undefined,
363
+ codeOwner: undefined,
356
364
  };
357
365
  const args = [...argv];
358
366
  while (args.length) {
@@ -363,6 +371,10 @@ function parseArgs(argv) {
363
371
  else if (a === "--yes" || a === "-y") out.yes = true;
364
372
  else if (a === "--force") out.force = true;
365
373
  else if (a === "--minimal") out.minimal = true;
374
+ else if (a === "--with-ci") out.ci = true;
375
+ else if (a === "--no-ci") out.ci = false;
376
+ else if (a === "--code-owner") out.codeOwner = args.shift();
377
+ else if (a?.startsWith("--code-owner=")) out.codeOwner = a.slice("--code-owner=".length);
366
378
  else if (a === "--install") out.install = true;
367
379
  else if (a === "--no-install") out.install = false;
368
380
  else if (a === "--git") out.git = true;
@@ -391,6 +403,11 @@ function detectPackageManager() {
391
403
  }
392
404
 
393
405
  const VALID_NAME = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
406
+ // Match the GitHub CODEOWNERS owner grammar: a personal handle (@user), an
407
+ // organization team (@org/team), or an email address. Anything else is
408
+ // rejected so the scaffolded CODEOWNERS stays meaningful for branch protection.
409
+ const VALID_CODE_OWNER =
410
+ /^(?:@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,38})(?:\/[a-zA-Z0-9][a-zA-Z0-9._-]*)?|[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,})$/;
394
411
 
395
412
  function validateProjectName(name) {
396
413
  if (!name || !name.trim()) return "Project name cannot be empty.";
@@ -521,9 +538,176 @@ function rewriteScriptsForPackageManager(scripts, pm) {
521
538
 
522
539
  async function normalizePackageManagerFiles(dir, packageManager) {
523
540
  if (packageManager === "pnpm") return;
524
- const npmrcPath = path.join(dir, ".npmrc");
525
- if (!existsSync(npmrcPath)) return;
526
- await rm(npmrcPath, { force: true });
541
+ // The hardened `.npmrc` and `pnpm-workspace.yaml` only make sense for pnpm.
542
+ // Removing them keeps npm/yarn/bun scaffolds from inheriting pnpm-specific
543
+ // settings the chosen package manager would either ignore or misinterpret.
544
+ for (const file of [".npmrc", "pnpm-workspace.yaml"]) {
545
+ const target = path.join(dir, file);
546
+ if (existsSync(target)) {
547
+ await rm(target, { force: true });
548
+ }
549
+ }
550
+ }
551
+
552
+ function hasPackageScript(packageJson, scriptName) {
553
+ return typeof packageJson?.scripts?.[scriptName] === "string";
554
+ }
555
+
556
+ function runScriptCommand(packageManager, scriptName) {
557
+ if (packageManager === "pnpm") return `pnpm ${scriptName}`;
558
+ if (packageManager === "npm") return scriptName === "test" ? "npm test" : `npm run ${scriptName}`;
559
+ if (packageManager === "yarn") return scriptName === "test" ? "yarn test" : `yarn run ${scriptName}`;
560
+ if (packageManager === "bun") return scriptName === "test" ? "bun test" : `bun run ${scriptName}`;
561
+ return `${packageManager} run ${scriptName}`;
562
+ }
563
+
564
+ function installCommand(packageManager) {
565
+ if (packageManager === "pnpm") return "pnpm install --frozen-lockfile --ignore-scripts";
566
+ if (packageManager === "npm") return "npm ci --ignore-scripts";
567
+ if (packageManager === "yarn") return "yarn install --frozen-lockfile --ignore-scripts";
568
+ if (packageManager === "bun") return "bun install --frozen-lockfile --ignore-scripts";
569
+ return `${packageManager} install`;
570
+ }
571
+
572
+ function auditCommand(packageManager) {
573
+ if (packageManager === "pnpm") return "pnpm audit --prod";
574
+ if (packageManager === "npm") return "npm audit --omit=dev";
575
+ if (packageManager === "yarn") return "yarn audit --groups dependencies";
576
+ if (packageManager === "bun") return "bun audit";
577
+ return "";
578
+ }
579
+
580
+ function setupPackageManagerStep(packageManager) {
581
+ if (packageManager === "pnpm") {
582
+ return ` - name: Set up pnpm
583
+ uses: pnpm/action-setup@ac6db6d3c1f721f886538a378a2d73e85697340a # v6
584
+ with:
585
+ version: 11.1.2
586
+ run_install: false`;
587
+ }
588
+ if (packageManager === "yarn") {
589
+ return ` - name: Enable Corepack
590
+ run: corepack enable`;
591
+ }
592
+ if (packageManager === "bun") return setupBunStep();
593
+ return "";
594
+ }
595
+
596
+ function setupBunStep() {
597
+ return ` - name: Set up Bun
598
+ uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
599
+ with:
600
+ bun-version: latest`;
601
+ }
602
+
603
+ function workflowStep(name, command) {
604
+ return ` - name: ${name}
605
+ run: ${command}`;
606
+ }
607
+
608
+ function multilineWorkflowStep(name, command) {
609
+ return ` - name: ${name}
610
+ run: |
611
+ ${command
612
+ .split("\n")
613
+ .map((line) => ` ${line}`)
614
+ .join("\n")}`;
615
+ }
616
+
617
+ async function readPackageJsonIfPresent(dir) {
618
+ const file = path.join(dir, "package.json");
619
+ if (!existsSync(file)) return null;
620
+ return JSON.parse(await readFile(file, "utf8"));
621
+ }
622
+
623
+ async function writePackageJson(dir, packageJson) {
624
+ await writeFile(path.join(dir, "package.json"), JSON.stringify(packageJson, null, 2) + "\n", "utf8");
625
+ }
626
+
627
+ async function addLockfileVerifyScript(dir) {
628
+ const packageJson = await readPackageJsonIfPresent(dir);
629
+ if (!packageJson) return;
630
+ packageJson.scripts ??= {};
631
+ packageJson.scripts["verify:lockfile"] = "node scripts/verify-lockfile-sources.mjs";
632
+ await writePackageJson(dir, packageJson);
633
+ }
634
+
635
+ function renderCiReplacements({ packageManager, template, packageJson, codeOwner }) {
636
+ const setupPm = setupPackageManagerStep(packageManager);
637
+ const needsBunRuntime = template === "bun-basic" && packageManager !== "bun";
638
+ const audit = auditCommand(packageManager);
639
+ const buildStep = hasPackageScript(packageJson, "build") ? workflowStep("Build", runScriptCommand(packageManager, "build")) : "";
640
+ const auditStep = audit ? workflowStep("Audit production dependencies", audit) : "";
641
+ const tagVersionCheck = `set -eu
642
+ tag_version="\${GITHUB_REF_NAME#v}"
643
+ pkg_version="$(node -p "require('./package.json').version")"
644
+ if [ "$tag_version" != "$pkg_version" ]; then
645
+ echo "::error::Tag $GITHUB_REF_NAME does not match package.json version $pkg_version"
646
+ exit 1
647
+ fi`;
648
+
649
+ return new Map([
650
+ ["__CODE_OWNER__", codeOwner],
651
+ ["__SETUP_PACKAGE_MANAGER_STEP__", setupPm],
652
+ ["__SETUP_BUN_RUNTIME_STEP__", needsBunRuntime ? setupBunStep() : ""],
653
+ ["__INSTALL_COMMAND__", installCommand(packageManager)],
654
+ ["__VERIFY_LOCKFILE_COMMAND__", runScriptCommand(packageManager, "verify:lockfile")],
655
+ ["__TYPECHECK_COMMAND__", runScriptCommand(packageManager, "typecheck")],
656
+ ["__TEST_COMMAND__", runScriptCommand(packageManager, "test")],
657
+ ["__BUILD_STEP__", buildStep],
658
+ ["__AUDIT_STEP__", auditStep],
659
+ ["__TAG_VERSION_CHECK_STEP__", multilineWorkflowStep("Verify tag matches package.json version", tagVersionCheck)],
660
+ ]);
661
+ }
662
+
663
+ async function replacePlaceholdersInTree(dir, replacements) {
664
+ const entries = await readdir(dir, { withFileTypes: true });
665
+ for (const entry of entries) {
666
+ const full = path.join(dir, entry.name);
667
+ if (entry.isDirectory()) {
668
+ if (entry.name === "node_modules" || entry.name === ".git") continue;
669
+ await replacePlaceholdersInTree(full, replacements);
670
+ continue;
671
+ }
672
+ if (!entry.isFile()) continue;
673
+ if (!CI_PLACEHOLDER_EXTENSIONS.has(path.extname(entry.name)) && !CI_PLACEHOLDER_FILES.has(entry.name)) {
674
+ continue;
675
+ }
676
+ const raw = await readFile(full, "utf8");
677
+ let next = raw;
678
+ for (const [placeholder, value] of replacements) {
679
+ next = next.replaceAll(placeholder, value);
680
+ }
681
+ if (next !== raw) await writeFile(full, next, "utf8");
682
+ }
683
+ }
684
+
685
+ async function copyCiBundle(targetDir, template, packageManager, skipPackageManager, codeOwner) {
686
+ const flavor = skipPackageManager ? "deno" : "node";
687
+ const sourceDir = path.join(CI_TEMPLATES_DIR, flavor);
688
+ if (!existsSync(sourceDir)) {
689
+ throw new Error(`CI template bundle "${flavor}" is missing from this CLI build.`);
690
+ }
691
+ await copyTemplate(sourceDir, targetDir);
692
+
693
+ const candidate = codeOwner?.trim() ?? "";
694
+ if (candidate && !VALID_CODE_OWNER.test(candidate)) {
695
+ throw new Error(
696
+ `Invalid --code-owner "${candidate}". Use a GitHub handle (@user), a team (@org/team), or an email address.`,
697
+ );
698
+ }
699
+ const owner = candidate || "@your-org/security-team";
700
+ if (skipPackageManager) {
701
+ await replacePlaceholdersInTree(targetDir, new Map([["__CODE_OWNER__", owner]]));
702
+ return;
703
+ }
704
+
705
+ await addLockfileVerifyScript(targetDir);
706
+ const packageJson = await readPackageJsonIfPresent(targetDir);
707
+ await replacePlaceholdersInTree(
708
+ targetDir,
709
+ renderCiReplacements({ packageManager, template, packageJson, codeOwner: owner }),
710
+ );
527
711
  }
528
712
 
529
713
  /**
@@ -858,7 +1042,7 @@ function createSpinner(initialMessage) {
858
1042
  };
859
1043
  }
860
1044
 
861
- function printSummary({ projectName, template, packageManager, installDeps, skipPackageManager }) {
1045
+ function printSummary({ projectName, template, packageManager, installDeps, skipPackageManager, withCi }) {
862
1046
  const templateMeta = TEMPLATE_OPTIONS.find((option) => option.value === template);
863
1047
  const templateLabel = templateMeta ? `${templateMeta.title} ${color(COLORS.dim, `(${template})`)}` : template;
864
1048
  const summaryLines = [
@@ -872,6 +1056,9 @@ function printSummary({ projectName, template, packageManager, installDeps, skip
872
1056
  } else {
873
1057
  summaryLines.push(`${color(COLORS.gray, "Manager ")} ${color(COLORS.cyan, packageManager)}`);
874
1058
  }
1059
+ if (withCi) {
1060
+ summaryLines.push(`${color(COLORS.gray, "Security ")} ${color(COLORS.cyan, "GitHub CI bundle")}`);
1061
+ }
875
1062
  console.log("");
876
1063
  console.log(renderBox(summaryLines, { accent: COLORS.green }));
877
1064
  console.log("");
@@ -1010,6 +1197,11 @@ async function main() {
1010
1197
  initGit = rl ? await askYesNo(rl, "Initialize a git repository?", true) : false;
1011
1198
  }
1012
1199
 
1200
+ let withCi = opts.ci;
1201
+ if (withCi === undefined) {
1202
+ withCi = rl ? await askYesNo(rl, "Add hardened GitHub Actions and security files?", false) : false;
1203
+ }
1204
+
1013
1205
  rl?.close();
1014
1206
 
1015
1207
  if (interactive) {
@@ -1036,6 +1228,11 @@ async function main() {
1036
1228
  }
1037
1229
  }
1038
1230
 
1231
+ if (withCi) {
1232
+ await copyCiBundle(targetDir, template, packageManager, skipPackageManager, opts.codeOwner);
1233
+ logStep("GitHub security bundle added", skipPackageManager ? "deno" : packageManager);
1234
+ }
1235
+
1039
1236
  if (initGit) {
1040
1237
  const code = await run("git", ["init", "--quiet"], targetDir);
1041
1238
  if (code === 0) {
@@ -1062,7 +1259,7 @@ async function main() {
1062
1259
  }
1063
1260
  }
1064
1261
 
1065
- printSummary({ projectName, template, packageManager, installDeps, skipPackageManager });
1262
+ printSummary({ projectName, template, packageManager, installDeps, skipPackageManager, withCi });
1066
1263
  } catch (err) {
1067
1264
  rl?.close();
1068
1265
  if (err && err.message === "Cancelled") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-daloy",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "description": "Scaffold a new DaloyJS project. Run with `pnpm create daloy`, `npm create daloy@latest`, `yarn create daloy`, or `bun create daloy`.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -0,0 +1,32 @@
1
+ # Security Policy
2
+
3
+ ## Supported versions
4
+
5
+ This project is generated by `create-daloy`. Apply security fixes to the
6
+ currently deployed branch and keep dependencies current through review.
7
+
8
+ ## Reporting a vulnerability
9
+
10
+ Do not open a public issue for suspected vulnerabilities. Use your company's
11
+ private intake process, GitHub private vulnerability reporting, or the security
12
+ contact for this repository.
13
+
14
+ ## Generated controls
15
+
16
+ The `--with-ci` bundle adds these defaults:
17
+
18
+ - GitHub Actions workflows use top-level `permissions: {}` and job-scoped permissions.
19
+ - Fork pull requests use `pull_request`, not `pull_request_target`.
20
+ - Third-party actions are pinned to commit SHAs.
21
+ - `actions/checkout` uses `persist-credentials: false`.
22
+ - Deno CI runs `deno task typecheck` and `deno task test` with the template's narrow permission flags.
23
+ - CodeQL, OpenSSF Scorecard, zizmor, Dependabot for GitHub Actions, and CODEOWNERS are generated.
24
+
25
+ ## Required repository settings
26
+
27
+ Before relying on these files for a company project:
28
+
29
+ 1. Replace `@your-org/security-team` in `.github/CODEOWNERS` or pass `--code-owner` when scaffolding.
30
+ 2. Protect the `main` branch and require the CI, CodeQL, Scorecard, and zizmor checks.
31
+ 3. Enable GitHub secret scanning and push protection.
32
+ 4. Keep Deno permissions narrow; do not switch tasks to `--allow-all`.
@@ -0,0 +1,17 @@
1
+ # Replace the placeholder owner with the GitHub user or team that must review
2
+ # privileged files before relying on CODEOWNERS protection.
3
+
4
+ * __CODE_OWNER__
5
+
6
+ # Workflow / CI / CD.
7
+ /.github/ __CODE_OWNER__
8
+ /.github/workflows/ __CODE_OWNER__
9
+ /.github/dependabot.yml __CODE_OWNER__
10
+ /.github/CODEOWNERS __CODE_OWNER__
11
+
12
+ # Files that affect dependency resolution or runtime permissions.
13
+ /deno.json __CODE_OWNER__
14
+ /deno.lock __CODE_OWNER__
15
+
16
+ # Vulnerability handling.
17
+ /SECURITY.md __CODE_OWNER__
@@ -0,0 +1,15 @@
1
+ version: 2
2
+
3
+ updates:
4
+ - package-ecosystem: github-actions
5
+ directory: "/"
6
+ schedule:
7
+ interval: weekly
8
+ day: monday
9
+ open-pull-requests-limit: 10
10
+ groups:
11
+ actions:
12
+ patterns:
13
+ - "*"
14
+ commit-message:
15
+ prefix: "chore(actions)"
@@ -0,0 +1,47 @@
1
+ # Hardened Deno CI generated by create-daloy --with-ci.
2
+ name: CI
3
+
4
+ on:
5
+ pull_request:
6
+ push:
7
+ branches:
8
+ - main
9
+
10
+ permissions: {}
11
+
12
+ concurrency:
13
+ group: ci-${{ github.workflow }}-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ jobs:
17
+ verify:
18
+ name: Verify
19
+ runs-on: ubuntu-latest
20
+ timeout-minutes: 20
21
+ permissions:
22
+ contents: read
23
+
24
+ steps:
25
+ - name: Harden runner
26
+ uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2
27
+ with:
28
+ egress-policy: audit
29
+ disable-sudo: true
30
+ disable-file-monitoring: false
31
+
32
+ - name: Checkout
33
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
34
+ with:
35
+ persist-credentials: false
36
+ show-progress: false
37
+
38
+ - name: Set up Deno
39
+ uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2.0.4
40
+ with:
41
+ deno-version: v2.x
42
+
43
+ - name: Typecheck
44
+ run: deno task typecheck
45
+
46
+ - name: Test
47
+ run: deno task test
@@ -0,0 +1,56 @@
1
+ name: CodeQL
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+ schedule:
9
+ - cron: "37 4 * * 1"
10
+
11
+ permissions: {}
12
+
13
+ jobs:
14
+ analyze:
15
+ name: Analyze (${{ matrix.language }})
16
+ runs-on: ubuntu-latest
17
+ timeout-minutes: 30
18
+ permissions:
19
+ security-events: write
20
+ packages: read
21
+ actions: read
22
+ contents: read
23
+
24
+ strategy:
25
+ fail-fast: false
26
+ matrix:
27
+ include:
28
+ - language: javascript-typescript
29
+ build-mode: none
30
+ - language: actions
31
+ build-mode: none
32
+
33
+ steps:
34
+ - name: Harden runner
35
+ uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2
36
+ with:
37
+ egress-policy: audit
38
+ disable-sudo: true
39
+
40
+ - name: Checkout
41
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
42
+ with:
43
+ persist-credentials: false
44
+ show-progress: false
45
+
46
+ - name: Initialize CodeQL
47
+ uses: github/codeql-action/init@52485aec7be33610227643b0fe83936b8b5f061a # v3
48
+ with:
49
+ languages: ${{ matrix.language }}
50
+ build-mode: ${{ matrix.build-mode }}
51
+ queries: security-extended,security-and-quality
52
+
53
+ - name: Perform CodeQL Analysis
54
+ uses: github/codeql-action/analyze@52485aec7be33610227643b0fe83936b8b5f061a # v3
55
+ with:
56
+ category: "/language:${{ matrix.language }}"
@@ -0,0 +1,46 @@
1
+ name: Scorecard
2
+
3
+ on:
4
+ branch_protection_rule:
5
+ schedule:
6
+ - cron: "23 5 * * 2"
7
+ push:
8
+ branches: [main]
9
+
10
+ permissions: {}
11
+
12
+ jobs:
13
+ analysis:
14
+ name: Scorecard analysis
15
+ runs-on: ubuntu-latest
16
+ timeout-minutes: 15
17
+ permissions:
18
+ security-events: write
19
+ id-token: write
20
+ contents: read
21
+ actions: read
22
+
23
+ steps:
24
+ - name: Harden runner
25
+ uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2
26
+ with:
27
+ egress-policy: audit
28
+ disable-sudo: true
29
+
30
+ - name: Checkout
31
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
32
+ with:
33
+ persist-credentials: false
34
+ show-progress: false
35
+
36
+ - name: Run analysis
37
+ uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
38
+ with:
39
+ results_file: results.sarif
40
+ results_format: sarif
41
+ publish_results: true
42
+
43
+ - name: Upload to code-scanning
44
+ uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
45
+ with:
46
+ sarif_file: results.sarif
@@ -0,0 +1,38 @@
1
+ name: Zizmor
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ schedule:
8
+ - cron: "0 7 * * *"
9
+
10
+ permissions: {}
11
+
12
+ jobs:
13
+ zizmor:
14
+ name: Lint workflows
15
+ runs-on: ubuntu-latest
16
+ timeout-minutes: 10
17
+ permissions:
18
+ security-events: write
19
+ contents: read
20
+ actions: read
21
+
22
+ steps:
23
+ - name: Harden runner
24
+ uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2
25
+ with:
26
+ egress-policy: audit
27
+ disable-sudo: true
28
+
29
+ - name: Checkout
30
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
31
+ with:
32
+ persist-credentials: false
33
+ show-progress: false
34
+
35
+ - name: Run zizmor
36
+ uses: zizmorcore/zizmor-action@b572f7b1a1c2d41efaab43d504f68d215c3cd727 # v0.5.4
37
+ with:
38
+ version: v1.25.0
@@ -0,0 +1,36 @@
1
+ # Security Policy
2
+
3
+ ## Supported versions
4
+
5
+ This project is generated by `create-daloy`. Apply security fixes to the
6
+ currently deployed branch and keep dependencies current through Dependabot.
7
+
8
+ ## Reporting a vulnerability
9
+
10
+ Do not open a public issue for suspected vulnerabilities. Use your company's
11
+ private intake process, GitHub private vulnerability reporting, or the security
12
+ contact for this repository.
13
+
14
+ ## Generated controls
15
+
16
+ The `--with-ci` bundle adds these defaults:
17
+
18
+ - GitHub Actions workflows use top-level `permissions: {}` and job-scoped permissions.
19
+ - Fork pull requests use `pull_request`, not `pull_request_target`.
20
+ - Third-party actions are pinned to commit SHAs.
21
+ - `actions/checkout` uses `persist-credentials: false`.
22
+ - Dependency installs run with lifecycle scripts disabled.
23
+ - Package-manager caches are disabled in CI to avoid cache-poisoning bridges.
24
+ - Lockfile source verification rejects git dependencies and non-registry tarball URLs.
25
+ - CodeQL, OpenSSF Scorecard, zizmor, Dependabot, and CODEOWNERS are generated.
26
+ - npm publishing is disabled until `NPM_PUBLISH_ENABLED=true` is set and npm trusted publishing is configured.
27
+
28
+ ## Required repository settings
29
+
30
+ Before relying on these files for a company project:
31
+
32
+ 1. Replace `@your-org/security-team` in `.github/CODEOWNERS` or pass `--code-owner` when scaffolding.
33
+ 2. Protect the `main` branch and require the CI, CodeQL, Scorecard, and zizmor checks.
34
+ 3. Enable GitHub secret scanning and push protection.
35
+ 4. Configure a protected `npm-publish` Environment before enabling npm publish.
36
+ 5. Keep `ignore-scripts=true` and the `pnpm-workspace.yaml` supply-chain settings on when using pnpm.
@@ -0,0 +1,25 @@
1
+ # Replace the placeholder owner with the GitHub user or team that must review
2
+ # privileged files before relying on CODEOWNERS protection.
3
+
4
+ * __CODE_OWNER__
5
+
6
+ # Workflow / CI / CD.
7
+ /.github/ __CODE_OWNER__
8
+ /.github/workflows/ __CODE_OWNER__
9
+ /.github/workflows/release.yml __CODE_OWNER__
10
+ /.github/dependabot.yml __CODE_OWNER__
11
+ /.github/CODEOWNERS __CODE_OWNER__
12
+
13
+ # Files that affect dependency resolution, install scripts, or publishing.
14
+ /package.json __CODE_OWNER__
15
+ /package-lock.json __CODE_OWNER__
16
+ /npm-shrinkwrap.json __CODE_OWNER__
17
+ /pnpm-lock.yaml __CODE_OWNER__
18
+ /pnpm-workspace.yaml __CODE_OWNER__
19
+ /yarn.lock __CODE_OWNER__
20
+ /bun.lock __CODE_OWNER__
21
+ /.npmrc __CODE_OWNER__
22
+ /scripts/verify-lockfile-sources.mjs __CODE_OWNER__
23
+
24
+ # Vulnerability handling.
25
+ /SECURITY.md __CODE_OWNER__
@@ -0,0 +1,28 @@
1
+ version: 2
2
+
3
+ updates:
4
+ - package-ecosystem: github-actions
5
+ directory: "/"
6
+ schedule:
7
+ interval: weekly
8
+ day: monday
9
+ open-pull-requests-limit: 10
10
+ groups:
11
+ actions:
12
+ patterns:
13
+ - "*"
14
+ commit-message:
15
+ prefix: "chore(actions)"
16
+
17
+ - package-ecosystem: npm
18
+ directory: "/"
19
+ schedule:
20
+ interval: weekly
21
+ day: monday
22
+ open-pull-requests-limit: 10
23
+ versioning-strategy: increase-if-necessary
24
+ groups:
25
+ dev-dependencies:
26
+ dependency-type: development
27
+ commit-message:
28
+ prefix: "chore(deps)"
@@ -0,0 +1,65 @@
1
+ # Hardened CI generated by create-daloy --with-ci.
2
+ name: CI
3
+
4
+ on:
5
+ pull_request:
6
+ push:
7
+ branches:
8
+ - main
9
+
10
+ permissions: {}
11
+
12
+ concurrency:
13
+ group: ci-${{ github.workflow }}-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ jobs:
17
+ verify:
18
+ name: Verify
19
+ runs-on: ubuntu-latest
20
+ timeout-minutes: 20
21
+ permissions:
22
+ contents: read
23
+
24
+ steps:
25
+ - name: Harden runner
26
+ uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2
27
+ with:
28
+ egress-policy: audit
29
+ disable-sudo: true
30
+ disable-file-monitoring: false
31
+
32
+ - name: Checkout
33
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
34
+ with:
35
+ persist-credentials: false
36
+ show-progress: false
37
+
38
+ __SETUP_PACKAGE_MANAGER_STEP__
39
+
40
+ - name: Set up Node.js
41
+ uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
42
+ with:
43
+ node-version: 24
44
+ # Package-manager caching is intentionally disabled. Shared caches
45
+ # can bridge fork PRs into trusted branches when mis-keyed.
46
+
47
+ __SETUP_BUN_RUNTIME_STEP__
48
+
49
+ - name: Install dependencies
50
+ run: __INSTALL_COMMAND__
51
+ env:
52
+ npm_config_ignore_scripts: "true"
53
+
54
+ - name: Verify lockfile sources
55
+ run: __VERIFY_LOCKFILE_COMMAND__
56
+
57
+ - name: Typecheck
58
+ run: __TYPECHECK_COMMAND__
59
+
60
+ - name: Test
61
+ run: __TEST_COMMAND__
62
+
63
+ __BUILD_STEP__
64
+
65
+ __AUDIT_STEP__
@@ -0,0 +1,56 @@
1
+ name: CodeQL
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+ schedule:
9
+ - cron: "37 4 * * 1"
10
+
11
+ permissions: {}
12
+
13
+ jobs:
14
+ analyze:
15
+ name: Analyze (${{ matrix.language }})
16
+ runs-on: ubuntu-latest
17
+ timeout-minutes: 30
18
+ permissions:
19
+ security-events: write
20
+ packages: read
21
+ actions: read
22
+ contents: read
23
+
24
+ strategy:
25
+ fail-fast: false
26
+ matrix:
27
+ include:
28
+ - language: javascript-typescript
29
+ build-mode: none
30
+ - language: actions
31
+ build-mode: none
32
+
33
+ steps:
34
+ - name: Harden runner
35
+ uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2
36
+ with:
37
+ egress-policy: audit
38
+ disable-sudo: true
39
+
40
+ - name: Checkout
41
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
42
+ with:
43
+ persist-credentials: false
44
+ show-progress: false
45
+
46
+ - name: Initialize CodeQL
47
+ uses: github/codeql-action/init@52485aec7be33610227643b0fe83936b8b5f061a # v3
48
+ with:
49
+ languages: ${{ matrix.language }}
50
+ build-mode: ${{ matrix.build-mode }}
51
+ queries: security-extended,security-and-quality
52
+
53
+ - name: Perform CodeQL Analysis
54
+ uses: github/codeql-action/analyze@52485aec7be33610227643b0fe83936b8b5f061a # v3
55
+ with:
56
+ category: "/language:${{ matrix.language }}"
@@ -0,0 +1,125 @@
1
+ # Hardened npm publish skeleton generated by create-daloy --with-ci.
2
+ #
3
+ # Publishing is disabled until you set the repository variable
4
+ # NPM_PUBLISH_ENABLED=true, remove "private": true from package.json, and
5
+ # configure npm trusted publishing for this repository/environment.
6
+ name: Publish
7
+
8
+ on:
9
+ push:
10
+ tags:
11
+ - "v*"
12
+ workflow_dispatch:
13
+
14
+ permissions: {}
15
+
16
+ concurrency:
17
+ group: publish-${{ github.ref }}
18
+ cancel-in-progress: false
19
+
20
+ jobs:
21
+ verify:
22
+ name: Verify before publish
23
+ runs-on: ubuntu-latest
24
+ timeout-minutes: 20
25
+ permissions:
26
+ contents: read
27
+ steps:
28
+ - name: Harden runner
29
+ uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2
30
+ with:
31
+ egress-policy: audit
32
+ disable-sudo: true
33
+
34
+ - name: Checkout
35
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
36
+ with:
37
+ persist-credentials: false
38
+ show-progress: false
39
+
40
+ __SETUP_PACKAGE_MANAGER_STEP__
41
+
42
+ - name: Set up Node.js
43
+ uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
44
+ with:
45
+ node-version: 24
46
+
47
+ __SETUP_BUN_RUNTIME_STEP__
48
+
49
+ - name: Install dependencies
50
+ run: __INSTALL_COMMAND__
51
+ env:
52
+ npm_config_ignore_scripts: "true"
53
+
54
+ - name: Verify lockfile sources
55
+ run: __VERIFY_LOCKFILE_COMMAND__
56
+
57
+ - name: Typecheck
58
+ run: __TYPECHECK_COMMAND__
59
+
60
+ - name: Test
61
+ run: __TEST_COMMAND__
62
+
63
+ __BUILD_STEP__
64
+
65
+ publish-npm:
66
+ name: Publish npm package
67
+ needs: verify
68
+ if: vars.NPM_PUBLISH_ENABLED == 'true'
69
+ runs-on: ubuntu-latest
70
+ timeout-minutes: 15
71
+ environment:
72
+ name: ${{ vars.NPM_PUBLISH_ENVIRONMENT || 'npm-publish' }}
73
+ url: https://www.npmjs.com/package/${{ github.event.repository.name }}
74
+ permissions:
75
+ contents: read
76
+ id-token: write
77
+
78
+ steps:
79
+ - name: Harden runner
80
+ uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2
81
+ with:
82
+ egress-policy: block
83
+ allowed-endpoints: >
84
+ api.github.com:443
85
+ github.com:443
86
+ objects.githubusercontent.com:443
87
+ registry.npmjs.org:443
88
+ fulcio.sigstore.dev:443
89
+ rekor.sigstore.dev:443
90
+ tuf-repo-cdn.sigstore.dev:443
91
+ disable-sudo: true
92
+
93
+ - name: Checkout
94
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
95
+ with:
96
+ persist-credentials: false
97
+ show-progress: false
98
+
99
+ __SETUP_PACKAGE_MANAGER_STEP__
100
+
101
+ - name: Set up Node.js
102
+ uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
103
+ with:
104
+ node-version: 24
105
+ registry-url: "https://registry.npmjs.org"
106
+
107
+ __SETUP_BUN_RUNTIME_STEP__
108
+
109
+ - name: Install dependencies
110
+ run: __INSTALL_COMMAND__
111
+ env:
112
+ npm_config_ignore_scripts: "true"
113
+
114
+ - name: Refuse private package publish
115
+ run: |
116
+ node -e "const pkg = require('./package.json'); if (pkg.private) { console.error('package.json is private; remove private:true before enabling npm publish'); process.exit(1); }"
117
+
118
+ __TAG_VERSION_CHECK_STEP__
119
+
120
+ __BUILD_STEP__
121
+
122
+ - name: Publish to npm with provenance
123
+ run: npm publish --access public --provenance
124
+ env:
125
+ NPM_CONFIG_PROVENANCE: "true"
@@ -0,0 +1,46 @@
1
+ name: Scorecard
2
+
3
+ on:
4
+ branch_protection_rule:
5
+ schedule:
6
+ - cron: "23 5 * * 2"
7
+ push:
8
+ branches: [main]
9
+
10
+ permissions: {}
11
+
12
+ jobs:
13
+ analysis:
14
+ name: Scorecard analysis
15
+ runs-on: ubuntu-latest
16
+ timeout-minutes: 15
17
+ permissions:
18
+ security-events: write
19
+ id-token: write
20
+ contents: read
21
+ actions: read
22
+
23
+ steps:
24
+ - name: Harden runner
25
+ uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2
26
+ with:
27
+ egress-policy: audit
28
+ disable-sudo: true
29
+
30
+ - name: Checkout
31
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
32
+ with:
33
+ persist-credentials: false
34
+ show-progress: false
35
+
36
+ - name: Run analysis
37
+ uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
38
+ with:
39
+ results_file: results.sarif
40
+ results_format: sarif
41
+ publish_results: true
42
+
43
+ - name: Upload to code-scanning
44
+ uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
45
+ with:
46
+ sarif_file: results.sarif
@@ -0,0 +1,38 @@
1
+ name: Zizmor
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ schedule:
8
+ - cron: "0 7 * * *"
9
+
10
+ permissions: {}
11
+
12
+ jobs:
13
+ zizmor:
14
+ name: Lint workflows
15
+ runs-on: ubuntu-latest
16
+ timeout-minutes: 10
17
+ permissions:
18
+ security-events: write
19
+ contents: read
20
+ actions: read
21
+
22
+ steps:
23
+ - name: Harden runner
24
+ uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2
25
+ with:
26
+ egress-policy: audit
27
+ disable-sudo: true
28
+
29
+ - name: Checkout
30
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
31
+ with:
32
+ persist-credentials: false
33
+ show-progress: false
34
+
35
+ - name: Run zizmor
36
+ uses: zizmorcore/zizmor-action@b572f7b1a1c2d41efaab43d504f68d215c3cd727 # v0.5.4
37
+ with:
38
+ version: v1.25.0
@@ -0,0 +1,61 @@
1
+ import { access, readFile } from "node:fs/promises";
2
+
3
+ const TEXT_LOCKFILES = ["pnpm-lock.yaml", "package-lock.json", "npm-shrinkwrap.json", "yarn.lock", "bun.lock"];
4
+ const BINARY_LOCKFILES = ["bun.lockb"];
5
+ const GIT_SOURCE_PATTERN = /(?:specifier:\s*)?(?:github:|git\+|git:\/\/|ssh:\/\/git@|git@github\.com:)/i;
6
+ const URL_PATTERN = /(?:tarball|resolved)["':\s]+(?<url>https?:\/\/[^"'\s},]+)/i;
7
+ const ALLOWED_TARBALL_PREFIXES = ["https://registry.npmjs.org/", "https://registry.yarnpkg.com/"];
8
+
9
+ async function exists(file) {
10
+ try {
11
+ await access(file);
12
+ return true;
13
+ } catch {
14
+ return false;
15
+ }
16
+ }
17
+
18
+ function findForbiddenSources(lockfile) {
19
+ const findings = [];
20
+ const lines = lockfile.split(/\r?\n/);
21
+ for (const [index, rawLine] of lines.entries()) {
22
+ const text = rawLine.trim();
23
+ if (GIT_SOURCE_PATTERN.test(text)) {
24
+ findings.push({ line: index + 1, reason: "git dependency source", text });
25
+ continue;
26
+ }
27
+
28
+ const url = URL_PATTERN.exec(text)?.groups?.url;
29
+ if (url && !ALLOWED_TARBALL_PREFIXES.some((prefix) => url.startsWith(prefix))) {
30
+ findings.push({ line: index + 1, reason: "non-registry tarball source", text });
31
+ }
32
+ }
33
+ return findings;
34
+ }
35
+
36
+ let checked = false;
37
+ let failed = false;
38
+
39
+ for (const file of TEXT_LOCKFILES) {
40
+ if (!(await exists(file))) continue;
41
+ checked = true;
42
+ const findings = findForbiddenSources(await readFile(file, "utf8"));
43
+ for (const finding of findings) {
44
+ failed = true;
45
+ console.error(`${file}: ${finding.reason} on line ${finding.line}: ${finding.text}`);
46
+ }
47
+ }
48
+
49
+ for (const file of BINARY_LOCKFILES) {
50
+ if (await exists(file)) {
51
+ failed = true;
52
+ console.error(`${file}: binary lockfiles cannot be source-verified; use a text lockfile in CI.`);
53
+ }
54
+ }
55
+
56
+ if (!checked) {
57
+ failed = true;
58
+ console.error("No text lockfile found. Commit a lockfile before relying on CI.");
59
+ }
60
+
61
+ if (failed) process.exitCode = 1;
@@ -0,0 +1,25 @@
1
+ # pnpm workspace-level supply-chain settings.
2
+ #
3
+ # This file is here even though the default package manager for this
4
+ # template is Bun, because users can switch to pnpm with
5
+ # `pnpm create daloy --package-manager pnpm`. When that happens, the
6
+ # scaffolder keeps this file so pnpm 11+ picks up the workspace-level
7
+ # supply-chain controls below. If you keep using Bun, you can delete it.
8
+ #
9
+ # pnpm 11+ reads `pnpm-workspace.yaml` as a settings file even when no
10
+ # `packages:` list is declared. It is the only file format that accepts
11
+ # the v11 keys below (`.npmrc` cannot express them).
12
+ #
13
+ # See the DaloyJS supply-chain notes and the 2026-05-11 TanStack
14
+ # postmortem (https://tanstack.com/blog/npm-supply-chain-compromise-postmortem)
15
+ # for why every setting below is on by default.
16
+
17
+ # Wait 24h (1440 minutes) before resolving a freshly published version.
18
+ # Most npm worm campaigns are detected and unpublished within hours.
19
+ # Set to 0 only for a real hotfix.
20
+ minimumReleaseAge: 1440
21
+
22
+ # Only direct dependencies may use exotic sources (git, tarball URLs).
23
+ # Transitive deps must resolve from the configured registry, which makes
24
+ # typosquatted-tarball and compromised-fork attacks much harder to land.
25
+ blockExoticSubdeps: true
@@ -0,0 +1,24 @@
1
+ # pnpm workspace-level supply-chain settings.
2
+ #
3
+ # This file looks unusual in a single-package project, but pnpm 11+ reads
4
+ # `pnpm-workspace.yaml` as a settings file even when no `packages:` list
5
+ # is declared. It is the only file format that accepts the v11 keys
6
+ # below (`.npmrc` cannot express them).
7
+ #
8
+ # See the DaloyJS supply-chain notes and the 2026-05-11 TanStack
9
+ # postmortem (https://tanstack.com/blog/npm-supply-chain-compromise-postmortem)
10
+ # for why every setting below is on by default.
11
+ #
12
+ # Want a real monorepo? Add a `packages:` key listing your sub-package
13
+ # globs (e.g. `packages: ["apps/*", "packages/*"]`) and the keys below
14
+ # will continue to apply.
15
+
16
+ # Wait 24h (1440 minutes) before resolving a freshly published version.
17
+ # Most npm worm campaigns are detected and unpublished within hours.
18
+ # Set to 0 only for a real hotfix.
19
+ minimumReleaseAge: 1440
20
+
21
+ # Only direct dependencies may use exotic sources (git, tarball URLs).
22
+ # Transitive deps must resolve from the configured registry, which makes
23
+ # typosquatted-tarball and compromised-fork attacks much harder to land.
24
+ blockExoticSubdeps: true
@@ -0,0 +1,24 @@
1
+ # pnpm workspace-level supply-chain settings.
2
+ #
3
+ # This file looks unusual in a single-package project, but pnpm 11+ reads
4
+ # `pnpm-workspace.yaml` as a settings file even when no `packages:` list
5
+ # is declared. It is the only file format that accepts the v11 keys
6
+ # below (`.npmrc` cannot express them).
7
+ #
8
+ # See the DaloyJS supply-chain notes and the 2026-05-11 TanStack
9
+ # postmortem (https://tanstack.com/blog/npm-supply-chain-compromise-postmortem)
10
+ # for why every setting below is on by default.
11
+ #
12
+ # Want a real monorepo? Add a `packages:` key listing your sub-package
13
+ # globs (e.g. `packages: ["apps/*", "packages/*"]`) and the keys below
14
+ # will continue to apply.
15
+
16
+ # Wait 24h (1440 minutes) before resolving a freshly published version.
17
+ # Most npm worm campaigns are detected and unpublished within hours.
18
+ # Set to 0 only for a real hotfix.
19
+ minimumReleaseAge: 1440
20
+
21
+ # Only direct dependencies may use exotic sources (git, tarball URLs).
22
+ # Transitive deps must resolve from the configured registry, which makes
23
+ # typosquatted-tarball and compromised-fork attacks much harder to land.
24
+ blockExoticSubdeps: true
@@ -0,0 +1,24 @@
1
+ # pnpm workspace-level supply-chain settings.
2
+ #
3
+ # This file looks unusual in a single-package project, but pnpm 11+ reads
4
+ # `pnpm-workspace.yaml` as a settings file even when no `packages:` list
5
+ # is declared. It is the only file format that accepts the v11 keys
6
+ # below (`.npmrc` cannot express them).
7
+ #
8
+ # See the DaloyJS supply-chain notes and the 2026-05-11 TanStack
9
+ # postmortem (https://tanstack.com/blog/npm-supply-chain-compromise-postmortem)
10
+ # for why every setting below is on by default.
11
+ #
12
+ # Want a real monorepo? Add a `packages:` key listing your sub-package
13
+ # globs (e.g. `packages: ["apps/*", "packages/*"]`) and the keys below
14
+ # will continue to apply.
15
+
16
+ # Wait 24h (1440 minutes) before resolving a freshly published version.
17
+ # Most npm worm campaigns are detected and unpublished within hours.
18
+ # Set to 0 only for a real hotfix.
19
+ minimumReleaseAge: 1440
20
+
21
+ # Only direct dependencies may use exotic sources (git, tarball URLs).
22
+ # Transitive deps must resolve from the configured registry, which makes
23
+ # typosquatted-tarball and compromised-fork attacks much harder to land.
24
+ blockExoticSubdeps: true