create-daloy 0.1.21 → 0.1.23
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 +42 -1
- package/bin/create-daloy.mjs +197 -6
- package/package.json +1 -1
- package/templates/_ci/deno/SECURITY.md +32 -0
- package/templates/_ci/deno/_github/CODEOWNERS +17 -0
- package/templates/_ci/deno/_github/dependabot.yml +15 -0
- package/templates/_ci/deno/_github/workflows/ci.yml +47 -0
- package/templates/_ci/deno/_github/workflows/codeql.yml +56 -0
- package/templates/_ci/deno/_github/workflows/scorecard.yml +46 -0
- package/templates/_ci/deno/_github/workflows/zizmor.yml +38 -0
- package/templates/_ci/node/SECURITY.md +36 -0
- package/templates/_ci/node/_github/CODEOWNERS +25 -0
- package/templates/_ci/node/_github/dependabot.yml +28 -0
- package/templates/_ci/node/_github/workflows/ci.yml +65 -0
- package/templates/_ci/node/_github/workflows/codeql.yml +56 -0
- package/templates/_ci/node/_github/workflows/release.yml +125 -0
- package/templates/_ci/node/_github/workflows/scorecard.yml +46 -0
- package/templates/_ci/node/_github/workflows/zizmor.yml +38 -0
- package/templates/_ci/node/scripts/verify-lockfile-sources.mjs +61 -0
- package/templates/bun-basic/package.json +1 -1
- package/templates/cloudflare-worker/package.json +1 -1
- package/templates/deno-basic/deno.json +2 -2
- package/templates/node-basic/package.json +1 -1
- package/templates/vercel-edge/package.json +1 -1
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
|
package/bin/create-daloy.mjs
CHANGED
|
@@ -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
|
{
|
|
@@ -43,10 +44,10 @@ const TEMPLATE_OPTIONS = [
|
|
|
43
44
|
];
|
|
44
45
|
|
|
45
46
|
const PACKAGE_MANAGER_OPTIONS = [
|
|
46
|
-
{ value: "pnpm", title: "pnpm", description: "Recommended
|
|
47
|
-
{ value: "npm", title: "npm", description: "Use the npm
|
|
48
|
-
{ value: "yarn", title: "Yarn", description: "
|
|
49
|
-
{ value: "bun", title: "Bun", description: "
|
|
47
|
+
{ value: "pnpm", title: "pnpm", description: "Recommended default with the hardened pnpm workspace settings" },
|
|
48
|
+
{ value: "npm", title: "npm", description: "Use the stock npm CLI with rewritten scripts and docs" },
|
|
49
|
+
{ value: "yarn", title: "Yarn", description: "Yarn workflow with rewritten scripts and lockfile-friendly installs" },
|
|
50
|
+
{ value: "bun", title: "Bun", description: "Bun package manager for fast installs; runtime templates stay Bun-native" },
|
|
50
51
|
];
|
|
51
52
|
|
|
52
53
|
const TEMPLATES = TEMPLATE_OPTIONS.map((option) => option.value);
|
|
@@ -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.";
|
|
@@ -532,6 +549,167 @@ async function normalizePackageManagerFiles(dir, packageManager) {
|
|
|
532
549
|
}
|
|
533
550
|
}
|
|
534
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
|
+
);
|
|
711
|
+
}
|
|
712
|
+
|
|
535
713
|
/**
|
|
536
714
|
* `--minimal` post-processor.
|
|
537
715
|
*
|
|
@@ -864,7 +1042,7 @@ function createSpinner(initialMessage) {
|
|
|
864
1042
|
};
|
|
865
1043
|
}
|
|
866
1044
|
|
|
867
|
-
function printSummary({ projectName, template, packageManager, installDeps, skipPackageManager }) {
|
|
1045
|
+
function printSummary({ projectName, template, packageManager, installDeps, skipPackageManager, withCi }) {
|
|
868
1046
|
const templateMeta = TEMPLATE_OPTIONS.find((option) => option.value === template);
|
|
869
1047
|
const templateLabel = templateMeta ? `${templateMeta.title} ${color(COLORS.dim, `(${template})`)}` : template;
|
|
870
1048
|
const summaryLines = [
|
|
@@ -878,6 +1056,9 @@ function printSummary({ projectName, template, packageManager, installDeps, skip
|
|
|
878
1056
|
} else {
|
|
879
1057
|
summaryLines.push(`${color(COLORS.gray, "Manager ")} ${color(COLORS.cyan, packageManager)}`);
|
|
880
1058
|
}
|
|
1059
|
+
if (withCi) {
|
|
1060
|
+
summaryLines.push(`${color(COLORS.gray, "Security ")} ${color(COLORS.cyan, "GitHub CI bundle")}`);
|
|
1061
|
+
}
|
|
881
1062
|
console.log("");
|
|
882
1063
|
console.log(renderBox(summaryLines, { accent: COLORS.green }));
|
|
883
1064
|
console.log("");
|
|
@@ -1016,6 +1197,11 @@ async function main() {
|
|
|
1016
1197
|
initGit = rl ? await askYesNo(rl, "Initialize a git repository?", true) : false;
|
|
1017
1198
|
}
|
|
1018
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
|
+
|
|
1019
1205
|
rl?.close();
|
|
1020
1206
|
|
|
1021
1207
|
if (interactive) {
|
|
@@ -1042,6 +1228,11 @@ async function main() {
|
|
|
1042
1228
|
}
|
|
1043
1229
|
}
|
|
1044
1230
|
|
|
1231
|
+
if (withCi) {
|
|
1232
|
+
await copyCiBundle(targetDir, template, packageManager, skipPackageManager, opts.codeOwner);
|
|
1233
|
+
logStep("GitHub security bundle added", skipPackageManager ? "deno" : packageManager);
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1045
1236
|
if (initGit) {
|
|
1046
1237
|
const code = await run("git", ["init", "--quiet"], targetDir);
|
|
1047
1238
|
if (code === 0) {
|
|
@@ -1068,7 +1259,7 @@ async function main() {
|
|
|
1068
1259
|
}
|
|
1069
1260
|
}
|
|
1070
1261
|
|
|
1071
|
-
printSummary({ projectName, template, packageManager, installDeps, skipPackageManager });
|
|
1262
|
+
printSummary({ projectName, template, packageManager, installDeps, skipPackageManager, withCi });
|
|
1072
1263
|
} catch (err) {
|
|
1073
1264
|
rl?.close();
|
|
1074
1265
|
if (err && err.message === "Cancelled") {
|
package/package.json
CHANGED
|
@@ -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,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;
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"gen:openapi": "deno run --allow-net --allow-env --allow-read --allow-write scripts/dump-openapi.ts"
|
|
9
9
|
},
|
|
10
10
|
"imports": {
|
|
11
|
-
"@daloyjs/core": "npm:@daloyjs/core@^0.5
|
|
12
|
-
"@daloyjs/core/": "npm:@daloyjs/core@^0.5
|
|
11
|
+
"@daloyjs/core": "npm:@daloyjs/core@^0.7.5",
|
|
12
|
+
"@daloyjs/core/": "npm:@daloyjs/core@^0.7.5/",
|
|
13
13
|
"zod": "npm:zod@^4.4.3"
|
|
14
14
|
},
|
|
15
15
|
"compilerOptions": {
|