create-daloy 0.34.3 → 0.35.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +74 -20
  2. package/bin/create-daloy.mjs +83 -17
  3. package/package.json +2 -2
  4. package/sbom.cdx.json +13 -9
  5. package/sbom.spdx.json +5 -5
  6. package/templates/_ci/deno/SECURITY.md +31 -2
  7. package/templates/_ci/deno/_github/CODEOWNERS +2 -0
  8. package/templates/_ci/deno/_github/workflows/container-scan.yml +42 -8
  9. package/templates/_ci/deno/_github/workflows/deploy.yml +68 -3
  10. package/templates/_ci/deno/_github/workflows/opengrep.yml +137 -0
  11. package/templates/_ci/deno/_github/workflows/osv-scan.yml +121 -0
  12. package/templates/_ci/deno/_github/workflows/secret-scan.yml +106 -0
  13. package/templates/_ci/node/SECURITY.md +100 -2
  14. package/templates/_ci/node/_github/CODEOWNERS +2 -0
  15. package/templates/_ci/node/_github/workflows/container-scan.yml +46 -10
  16. package/templates/_ci/node/_github/workflows/opengrep.yml +169 -0
  17. package/templates/_ci/node/_github/workflows/osv-scan.yml +135 -0
  18. package/templates/_ci/node/_github/workflows/secret-scan.yml +106 -0
  19. package/templates/bun-basic/AGENTS.md +12 -0
  20. package/templates/bun-basic/_gitignore +5 -0
  21. package/templates/bun-basic/package.json +3 -2
  22. package/templates/cloudflare-worker/AGENTS.md +13 -0
  23. package/templates/cloudflare-worker/_gitignore +9 -0
  24. package/templates/cloudflare-worker/_npmrc +2 -1
  25. package/templates/cloudflare-worker/package.json +3 -2
  26. package/templates/deno-basic/AGENTS.md +12 -0
  27. package/templates/deno-basic/_gitignore +5 -0
  28. package/templates/deno-basic/deno.json +2 -2
  29. package/templates/node-basic/AGENTS.md +12 -0
  30. package/templates/node-basic/_gitignore +5 -0
  31. package/templates/node-basic/_npmrc +2 -2
  32. package/templates/node-basic/package.json +1 -1
  33. package/templates/vercel-edge/AGENTS.md +12 -0
  34. package/templates/vercel-edge/_gitignore +5 -0
  35. package/templates/vercel-edge/_npmrc +2 -1
  36. package/templates/vercel-edge/package.json +3 -2
package/README.md CHANGED
@@ -53,7 +53,7 @@ pnpm create daloy@latest my-api \
53
53
  | `--template <name>` | `node-basic` (default), `vercel-edge`, `cloudflare-worker`, `bun-basic`, or `deno-basic`. |
54
54
  | `--package-manager <pm>` | `pnpm` (default), `npm`, `yarn`, or `bun`. Ignored for `deno-basic`. |
55
55
  | `--list-templates` | Print available templates with descriptions. |
56
- | `--install` / `--no-install` | Install dependencies after scaffolding. Defaults to **Y** for npm/yarn/bun and **N** for pnpm (so first-time runs are not blocked by the 24h `minimumReleaseAge` embargo or the `pnpm.onlyBuiltDependencies` allowlist required by the hardened `.npmrc`). |
56
+ | `--install` / `--no-install` | Install dependencies after scaffolding. Defaults to **Y** for npm/yarn/bun and **N** for pnpm so you can review the hardened `.npmrc` / `pnpm-workspace.yaml` and aren't blocked by the 24h `minimumReleaseAge` embargo on the first run. |
57
57
  | `--git` / `--no-git` | Initialize a git repository. Defaults to interactive. |
58
58
  | `--minimal` | Strip the bookstore demo route and the built-in `/docs` + `/openapi.json` routes so only the framework bootstrap and `/healthz` ship. |
59
59
  | `--with-ci` / `--no-ci` | Add the hardened GitHub Actions, Dependabot, CODEOWNERS, SECURITY.md, and lockfile-source verification bundle. **Defaults to Y** so scaffolded projects are secure by default. |
@@ -86,8 +86,7 @@ A minimal Cloudflare Worker bootstrap using `@daloyjs/core/cloudflare` with:
86
86
 
87
87
  - `wrangler.toml` ready to deploy.
88
88
  - `secureHeaders` and `requestId` enabled by default, with smaller edge-friendly body and timeout limits.
89
- - Zod-validated route exposed as `fetch`.
90
- - A sample test that exercises `app.request(...)`.
89
+ - A Zod-validated `/healthz` route and contract-first `/books/:id` route exposed via `toFetchHandler(app)`.
91
90
 
92
91
  ### `vercel-edge`
93
92
 
@@ -149,25 +148,70 @@ pnpm create daloy@latest my-api \
149
148
  --code-owner @acme/security
150
149
  ```
151
150
 
152
- For Node-style templates, the bundle adds:
151
+ For Node-style templates, the bundle adds the following.
153
152
 
154
- - `.github/workflows/ci.yml` with top-level `permissions: {}`, pinned actions,
155
- `harden-runner`, `persist-credentials: false`, no package-manager cache, and
153
+ **CI and deploy**
154
+
155
+ - `.github/workflows/ci.yml` — top-level `permissions: {}`, pinned actions,
156
+ `harden-runner`, `persist-credentials: false`, no package-manager cache,
156
157
  install scripts disabled.
157
- - `.github/workflows/deploy.yml` as a manual-only deployment starter. Container
158
- templates publish a Docker image to GHCR with the repo-scoped `GITHUB_TOKEN`,
159
- while Vercel and Cloudflare templates ship concrete CLI deploy steps that
160
- read their platform credentials from GitHub Actions secrets/variables. The
161
- deploy job is gated to `main` or a tag by default, and Node-style templates
162
- re-run `verify:lockfile` before shipping.
163
- - `.github/workflows/vuln-scan.yml` — a daily scheduled SCA cron that runs the
164
- package manager's audit against the committed lockfile. Catches CVEs disclosed
158
+ - `.github/workflows/deploy.yml` a manual-only deployment starter, gated to
159
+ `main` or a tag by default. Container templates publish a Docker image to
160
+ GHCR with the repo-scoped `GITHUB_TOKEN`. Vercel and Cloudflare templates
161
+ ship concrete CLI deploy steps that read platform credentials from GitHub
162
+ Actions secrets / variables. Node-style templates re-run `verify:lockfile`
163
+ before shipping.
164
+
165
+ **Scheduled vulnerability scanning (SCA)**
166
+
167
+ - `.github/workflows/vuln-scan.yml` — daily cron that runs the package
168
+ manager's audit against the committed lockfile. Catches CVEs disclosed
165
169
  *after* the last PR or push and provides SOC 2 CC7.1
166
170
  ([continuous vulnerability management](https://www.aikido.dev/blog/a-guide-to-automating-technical-vulnerability-management-for-soc-2))
167
- evidence even when developers are not touching the repo.
168
- - CodeQL, OpenSSF Scorecard, zizmor, Dependabot, CODEOWNERS, and `SECURITY.md`.
171
+ evidence even when nobody is touching the repo.
172
+ - `.github/workflows/osv-scan.yml` a second, independent SCA source.
173
+ `vuln-scan.yml` queries the package manager's audit feed (GHSA); this one
174
+ runs Google's OSV-Scanner against the committed lockfile and cross-references
175
+ the OpenSSF [malicious-packages](https://github.com/ossf/malicious-packages)
176
+ corpus, so a malware advisory that lands in OSV.dev before it propagates to
177
+ GHSA still fails the build. The binary is downloaded from a pinned official
178
+ release and verified by SHA-256 before execution — no third-party action is
179
+ added to the supply chain just for this scan. This is the missing layer that
180
+ Aikido's [SAST vs SCA](https://www.aikido.dev/blog/sast-vs-sca) and
181
+ [npm-audit-guide](https://www.aikido.dev/blog/npm-audit-guide) write-ups warn
182
+ about, and the Deno scaffold gets it too (Deno has no `audit` built in, so
183
+ without OSV-Scanner a Deno scaffold would have no scheduled SCA at all).
184
+
185
+ **Secret and static analysis**
186
+
187
+ - `.github/workflows/secret-scan.yml` — runs [gitleaks](https://github.com/gitleaks/gitleaks)
188
+ on every PR / push (working tree) and on a daily schedule across the **full
189
+ git history**, so a credential leaked anywhere in any commit, branch, or tag
190
+ is surfaced even if GitHub-native push protection missed it. Binary is
191
+ pinned-release + SHA-256-verified before execution. See Aikido's
192
+ [Secrets Detection guide](https://www.aikido.dev/blog/secret-detection-application-security)
193
+ for why history-aware scanning is the floor, not the ceiling.
194
+ - `.github/workflows/opengrep.yml` — a second SAST source alongside CodeQL,
195
+ using [Opengrep](https://github.com/opengrep/opengrep) (an open-source
196
+ Semgrep fork) with the same pinned-binary + SHA-256-verified pattern.
197
+ - CodeQL (built in via the GitHub bundle).
198
+
199
+ **Container and runtime scanning**
200
+
201
+ - `.github/workflows/container-scan.yml` — runs Trivy against the image
202
+ produced by the template's `_Dockerfile` (filesystem scan on PR, full image
203
+ scan on push to `main`) so a base-image CVE or a vulnerable layer is
204
+ surfaced before deploy.
205
+ - `.github/workflows/dast.yml` — a manual-only dynamic-analysis workflow that
206
+ boots the scaffolded API and runs an OWASP ZAP baseline scan against it,
207
+ for teams that want a black-box check before promoting a release.
208
+
209
+ **Governance**
210
+
211
+ - OpenSSF Scorecard, zizmor, Dependabot, CODEOWNERS, and `SECURITY.md`.
169
212
  - `scripts/verify-lockfile-sources.mjs` plus a `verify:lockfile` package script
170
- that rejects git dependencies and non-registry tarball URLs in text lockfiles.
213
+ that rejects git dependencies and non-registry tarball URLs in text
214
+ lockfiles.
171
215
 
172
216
  The bundle deliberately does **not** generate an npm publish workflow.
173
217
  `create-daloy` scaffolds REST API services, not libraries; if you later carve
@@ -175,8 +219,9 @@ out a reusable package, opt into npm trusted publishing yourself.
175
219
 
176
220
  For `deno-basic`, `--with-ci` generates a Deno-native CI workflow, a manual-only
177
221
  container publish starter for GHCR that is guarded to `main` or a tag by
178
- default, plus CodeQL, Scorecard, zizmor, Dependabot for GitHub Actions,
179
- CODEOWNERS, and `SECURITY.md`.
222
+ default, plus CodeQL, Opengrep, **OSV-Scanner** (the only scheduled SCA layer
223
+ a Deno scaffold has, since Deno ships no `audit`), Scorecard, zizmor,
224
+ Dependabot for GitHub Actions, CODEOWNERS, and `SECURITY.md`.
180
225
 
181
226
  If you want the governance bundle but not the deployment starter, pass
182
227
  `--with-ci --no-deploy`. If you only want a deployment starter, pass
@@ -187,12 +232,21 @@ If you omit `--code-owner`, the generated CODEOWNERS file uses
187
232
  protection. You should also enable GitHub secret scanning, push protection, and
188
233
  required status checks in the repository settings.
189
234
 
235
+ ## Container-first scaffolds
236
+
237
+ Every template (Node, Bun, Vercel Edge, Cloudflare Worker, and Deno) ships a
238
+ production-oriented `Dockerfile` and `.dockerignore` with secure-by-default
239
+ posture: a non-root user, `STOPSIGNAL SIGTERM`, `tini` as PID 1, and a
240
+ `HEALTHCHECK` pointed at `/readyz`. Node-style templates also ship an
241
+ `.env.example`. None of this is required — delete or replace whatever you do
242
+ not need.
243
+
190
244
  ## What the CLI guarantees
191
245
 
192
246
  - Zero runtime dependencies (uses only Node built-ins) for a clean supply-chain footprint.
193
247
  - A modern terminal experience with Unicode/color capability detection and ASCII fallbacks.
194
248
  - Templates are copied verbatim from this package's `templates/` directory.
195
- - Files and folders prefixed with `_` are renamed on copy (`_gitignore` → `.gitignore`, `_npmrc` → `.npmrc`, `_github/` → `.github`, `_agents/` → `.agents/`) to survive npm packing.
249
+ - Files and folders prefixed with `_` are renamed on copy (`_gitignore` → `.gitignore`, `_npmrc` → `.npmrc`, `_github/` → `.github`, `_agents/` → `.agents/`, `_Dockerfile` → `Dockerfile`, `_dockerignore` → `.dockerignore`, `_env.example` → `.env.example`) to survive npm packing.
196
250
  - pnpm-specific `.npmrc` hardening is kept only when you choose `pnpm`; other package managers get a clean project without unsupported config warnings.
197
251
  - 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.
198
252
  - `--with-ci` projects ship with pinned GitHub Actions workflows, CODEOWNERS, Dependabot, SECURITY.md, and lockfile-source verification.
@@ -329,7 +329,7 @@ ${heading("Options")}
329
329
  ${color(COLORS.green, "--template <name>")} ${TEMPLATES.join(" | ")} ${color(COLORS.dim, "(default: node-basic)")}
330
330
  ${color(COLORS.green, "--package-manager <pm>")} ${PACKAGE_MANAGERS.join(" | ")} ${color(COLORS.dim, "(default: pnpm)")}
331
331
  ${color(COLORS.green, "--list-templates")} Print available templates and exit.
332
- ${color(COLORS.green, "--install / --no-install")} Install dependencies after scaffolding. ${color(COLORS.dim, "(default: Y, except pnpm \u2014 N to respect minimumReleaseAge + onlyBuiltDependencies)")}
332
+ ${color(COLORS.green, "--install / --no-install")} Install dependencies after scaffolding. ${color(COLORS.dim, "(default: Y, except pnpm \u2014 N to respect minimumReleaseAge + ignore-scripts review)")}
333
333
  ${color(COLORS.green, "--git / --no-git")} Initialize a git repository.
334
334
  ${color(COLORS.green, "--minimal")} Strip the bookstore + OpenAPI docs demo routes.
335
335
  ${color(COLORS.green, "--with-ci / --no-ci")} Add hardened GitHub Actions + governance files. ${color(COLORS.dim, "(default: Y)")}
@@ -791,11 +791,18 @@ function containerRegistryHeader() {
791
791
  # After the image is published, point your platform at GHCR or replace the
792
792
  # final push steps with your provider's deploy command.
793
793
  #
794
+ # Every pushed image is also signed and attested with Sigstore Cosign
795
+ # (keyless / OIDC) and an SPDX SBOM, so consumers can verify provenance
796
+ # with \`cosign verify\` and \`cosign verify-attestation --type spdxjson\`
797
+ # instead of trusting the registry alone. See Aikido's "Container
798
+ # Security Best Practices" (https://www.aikido.dev/blog/container-security-best-practices)
799
+ # for why signed images + SBOM attestation are the supply-chain floor.
800
+ #
794
801
  # Optional container-host handoff examples:
795
802
  # - Fly.io: install flyctl in a later step and run
796
- # flyctl deploy --image "$IMAGE_REPO:sha-\${GITHUB_SHA}" --remote-only
803
+ # flyctl deploy --image "$IMAGE_REPO@\${IMAGE_DIGEST}" --remote-only
797
804
  # - Render: create an image-backed service that tracks
798
- # ghcr.io/<owner>/<repo>:sha-\${GITHUB_SHA}`;
805
+ # ghcr.io/<owner>/<repo>@\${IMAGE_DIGEST}`;
799
806
  }
800
807
 
801
808
  function containerPublishSteps() {
@@ -828,7 +835,60 @@ function containerPublishSteps() {
828
835
  run: |
829
836
  set -eu
830
837
  docker push "$IMAGE_REPO:sha-\${GITHUB_SHA}"
831
- docker push "$IMAGE_REPO:latest"`;
838
+ docker push "$IMAGE_REPO:latest"
839
+
840
+ - name: Resolve pushed image digest
841
+ # Resolve and pin the immutable digest the rest of the workflow
842
+ # signs and attests against. Signing a mutable tag would let any
843
+ # later push silently re-point a "verified" tag at attacker
844
+ # content; binding cosign + the SBOM attestation to
845
+ # \${IMAGE_REPO}@sha256:<digest> closes that race.
846
+ run: |
847
+ set -eu
848
+ digest="$(docker inspect --format='{{index .RepoDigests 0}}' "$IMAGE_REPO:sha-\${GITHUB_SHA}" | awk -F@ '{print $2}')"
849
+ if [ -z "$digest" ]; then
850
+ echo "::error::Failed to resolve image digest for $IMAGE_REPO:sha-\${GITHUB_SHA}" >&2
851
+ exit 1
852
+ fi
853
+ echo "IMAGE_DIGEST=$digest" >> "$GITHUB_ENV"
854
+ echo "IMAGE_REF=$IMAGE_REPO@$digest" >> "$GITHUB_ENV"
855
+ echo "::notice::Signed image reference: $IMAGE_REPO@$digest"
856
+
857
+ - name: Install Cosign
858
+ # SHA-pinned per the project's actions-pinning policy. v4.1.2,
859
+ # commit 6f9f17788090df1f26f669e9d70d6ae9567deba6.
860
+ uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
861
+
862
+ - name: Sign image with Cosign (keyless / OIDC)
863
+ # Keyless signing uses the workflow's \`id-token\` OIDC identity
864
+ # — no long-lived signing key to leak. Verifiers can pin to
865
+ # \`--certificate-identity\` matching this workflow URL.
866
+ env:
867
+ COSIGN_EXPERIMENTAL: "1"
868
+ run: |
869
+ set -eu
870
+ cosign sign --yes "$IMAGE_REF"
871
+
872
+ - name: Generate SPDX SBOM for image
873
+ # SHA-pinned per the project's actions-pinning policy. v0.24.0,
874
+ # commit e22c389904149dbc22b58101806040fa8d37a610.
875
+ uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
876
+ with:
877
+ image: \${{ env.IMAGE_REF }}
878
+ format: spdx-json
879
+ output-file: sbom.spdx.json
880
+ upload-artifact: false
881
+ upload-release-assets: false
882
+
883
+ - name: Attest SBOM with Cosign
884
+ env:
885
+ COSIGN_EXPERIMENTAL: "1"
886
+ run: |
887
+ set -eu
888
+ cosign attest --yes \
889
+ --predicate sbom.spdx.json \
890
+ --type spdxjson \
891
+ "$IMAGE_REF"`;
832
892
  }
833
893
 
834
894
  function vercelDeployHeader() {
@@ -904,7 +964,11 @@ function renderDeployConfig({ template, packageManager, needsBunRuntime }) {
904
964
  return {
905
965
  header: containerRegistryHeader(),
906
966
  jobName: "Publish container image",
907
- jobPermissions: " packages: write",
967
+ // `packages: write` lets us push to GHCR; `id-token: write` lets
968
+ // Cosign mint a short-lived OIDC identity for keyless image signing
969
+ // + SBOM attestation. Both are scoped to this single job — the
970
+ // top-level workflow still has `permissions: {}`.
971
+ jobPermissions: " packages: write\n id-token: write",
908
972
  setupSteps: "",
909
973
  steps: containerPublishSteps(),
910
974
  };
@@ -1005,8 +1069,8 @@ async function replacePlaceholdersInTree(dir, replacements) {
1005
1069
  async function pruneCiBundle(targetDir, flavor, { includeSecurityBundle, includeDeployWorkflow }) {
1006
1070
  if (!includeSecurityBundle) {
1007
1071
  const workflowFiles = flavor === "deno"
1008
- ? ["ci.yml", "codeql.yml", "container-scan.yml", "dast.yml", "scorecard.yml", "zizmor.yml"]
1009
- : ["ci.yml", "codeql.yml", "container-scan.yml", "dast.yml", "scorecard.yml", "vuln-scan.yml", "zizmor.yml"];
1072
+ ? ["ci.yml", "codeql.yml", "container-scan.yml", "dast.yml", "opengrep.yml", "osv-scan.yml", "scorecard.yml", "secret-scan.yml", "zizmor.yml"]
1073
+ : ["ci.yml", "codeql.yml", "container-scan.yml", "dast.yml", "opengrep.yml", "osv-scan.yml", "scorecard.yml", "secret-scan.yml", "vuln-scan.yml", "zizmor.yml"];
1010
1074
  for (const file of workflowFiles) {
1011
1075
  await rm(path.join(targetDir, ".github", "workflows", file), { force: true });
1012
1076
  }
@@ -1489,10 +1553,13 @@ function printSummary({ projectName, template, packageManager, installDeps, skip
1489
1553
  ` ${color(COLORS.gray, SYMBOLS.pointer)} ${color(COLORS.dim, "(including a just-released @daloyjs/core) are embargoed for 24 h.")}`,
1490
1554
  );
1491
1555
  console.log(
1492
- ` ${color(COLORS.gray, SYMBOLS.pointer)} ${color(COLORS.dim, "Lifecycle scripts are blocked by default; allowlist trusted builds in")}`,
1556
+ ` ${color(COLORS.gray, SYMBOLS.pointer)} ${color(COLORS.dim, "Lifecycle scripts stay blocked by default (ignore-scripts=true);")}`,
1557
+ );
1558
+ console.log(
1559
+ ` ${color(COLORS.gray, SYMBOLS.pointer)} ${color(COLORS.dim, "if you later tighten build-script policy, keep exceptions in")}`,
1493
1560
  );
1494
1561
  console.log(
1495
- ` ${color(COLORS.gray, SYMBOLS.pointer)} ${color(COLORS.dim, "package.json under pnpm.onlyBuiltDependencies if install complains.")}`,
1562
+ ` ${color(COLORS.gray, SYMBOLS.pointer)} ${color(COLORS.dim, "pnpm-workspace.yaml under allowBuilds instead of weakening .npmrc.")}`,
1496
1563
  );
1497
1564
  }
1498
1565
 
@@ -1613,18 +1680,17 @@ async function main() {
1613
1680
  } else if (packageManager === "pnpm") {
1614
1681
  // Deny-by-default for pnpm: the scaffolded `pnpm-workspace.yaml` ships
1615
1682
  // with `minimumReleaseAge: 1440` (24 h embargo on newly-published
1616
- // versions) and the `.npmrc` blocks lifecycle scripts unless they're
1617
- // allowlisted in `package.json` under `pnpm.onlyBuiltDependencies`.
1618
- // Both are security best practices, but they mean a fresh
1619
- // `pnpm install` can fail until the user (a) waits 24 h for newly
1620
- // published `@daloyjs/core` versions to clear the embargo, or (b)
1621
- // allowlists any dep that needs a build script. Defaulting to N
1622
- // makes that explicit instead of failing the install silently.
1683
+ // versions), and the scaffolded `.npmrc` keeps `ignore-scripts=true`.
1684
+ // Both are security best practices, but they mean the first install
1685
+ // should be a deliberate user choice rather than something the CLI
1686
+ // does implicitly. Defaulting to N makes that explicit, and it also
1687
+ // avoids surprising users when a fresh `@daloyjs/core` release is
1688
+ // still inside the 24 h embargo window.
1623
1689
  if (rl) {
1624
1690
  console.log(
1625
1691
  color(
1626
1692
  COLORS.gray,
1627
- " (pnpm install may fail until you set pnpm.onlyBuiltDependencies in package.json and wait 24h for fresh @daloyjs/core releases \u2014 see pnpm-workspace.yaml)",
1693
+ " (pnpm install may wait out a fresh-release embargo and keeps lifecycle scripts blocked by default \u2014 see .npmrc + pnpm-workspace.yaml)",
1628
1694
  ),
1629
1695
  );
1630
1696
  installDeps = await askYesNo(rl, `Install dependencies with ${packageManager}?`, false);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-daloy",
3
- "version": "0.34.3",
3
+ "version": "0.35.1",
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",
@@ -45,4 +45,4 @@
45
45
  "scripts": {
46
46
  "test": "node --test test/**/*.test.mjs"
47
47
  }
48
- }
48
+ }
package/sbom.cdx.json CHANGED
@@ -1,25 +1,25 @@
1
1
  {
2
2
  "bomFormat": "CycloneDX",
3
3
  "specVersion": "1.5",
4
- "serialNumber": "urn:uuid:9fb86079-cf96-5a72-94a8-dd7d3cdc62cc",
4
+ "serialNumber": "urn:uuid:c11f2b19-2472-59a3-9312-536e039ae8be",
5
5
  "version": 1,
6
6
  "metadata": {
7
- "timestamp": "2026-05-23T13:34:45.760Z",
7
+ "timestamp": "2026-05-26T23:20:18.298Z",
8
8
  "tools": [
9
9
  {
10
10
  "vendor": "DaloyJS",
11
11
  "name": "daloy-generate-sbom",
12
- "version": "0.34.3"
12
+ "version": "0.35.1"
13
13
  }
14
14
  ],
15
15
  "authors": [],
16
16
  "component": {
17
17
  "type": "library",
18
- "bom-ref": "pkg:npm/create-daloy@0.34.3",
18
+ "bom-ref": "pkg:npm/create-daloy@0.35.1",
19
19
  "name": "create-daloy",
20
- "version": "0.34.3",
20
+ "version": "0.35.1",
21
21
  "description": "Scaffold a new DaloyJS project. Run with `pnpm create daloy`, `npm create daloy@latest`, `yarn create daloy`, or `bun create daloy`.",
22
- "purl": "pkg:npm/create-daloy@0.34.3",
22
+ "purl": "pkg:npm/create-daloy@0.35.1",
23
23
  "licenses": [
24
24
  {
25
25
  "license": {
@@ -35,12 +35,16 @@
35
35
  {
36
36
  "type": "website",
37
37
  "url": "https://github.com/daloyjs/daloy/tree/main/packages/create-daloy#readme"
38
+ },
39
+ {
40
+ "type": "distribution",
41
+ "url": "https://www.npmjs.com/package/create-daloy"
38
42
  }
39
43
  ],
40
44
  "swid": {
41
- "tagId": "swidtag-create-daloy-0.34.3",
45
+ "tagId": "swidtag-create-daloy-0.35.1",
42
46
  "name": "create-daloy",
43
- "version": "0.34.3",
47
+ "version": "0.35.1",
44
48
  "tagVersion": 0,
45
49
  "patch": false
46
50
  }
@@ -49,7 +53,7 @@
49
53
  "components": [],
50
54
  "dependencies": [
51
55
  {
52
- "ref": "pkg:npm/create-daloy@0.34.3",
56
+ "ref": "pkg:npm/create-daloy@0.35.1",
53
57
  "dependsOn": []
54
58
  }
55
59
  ]
package/sbom.spdx.json CHANGED
@@ -2,10 +2,10 @@
2
2
  "spdxVersion": "SPDX-2.3",
3
3
  "dataLicense": "CC0-1.0",
4
4
  "SPDXID": "SPDXRef-DOCUMENT",
5
- "name": "create-daloy-0.34.3",
6
- "documentNamespace": "https://github.com/daloyjs/daloy/sbom/create-daloy-0.34.3-9fb86079-cf96-5a72-94a8-dd7d3cdc62cc",
5
+ "name": "create-daloy-0.35.1",
6
+ "documentNamespace": "https://github.com/daloyjs/daloy/sbom/create-daloy-0.35.1-c11f2b19-2472-59a3-9312-536e039ae8be",
7
7
  "creationInfo": {
8
- "created": "2026-05-23T13:34:45.760Z",
8
+ "created": "2026-05-26T23:20:18.298Z",
9
9
  "creators": [
10
10
  "Tool: daloy-generate-sbom",
11
11
  "Organization: DaloyJS"
@@ -16,7 +16,7 @@
16
16
  {
17
17
  "SPDXID": "SPDXRef-Package-create-daloy",
18
18
  "name": "create-daloy",
19
- "versionInfo": "0.34.3",
19
+ "versionInfo": "0.35.1",
20
20
  "downloadLocation": "https://github.com/daloyjs/daloy",
21
21
  "filesAnalyzed": false,
22
22
  "licenseConcluded": "MIT",
@@ -27,7 +27,7 @@
27
27
  {
28
28
  "referenceCategory": "PACKAGE-MANAGER",
29
29
  "referenceType": "purl",
30
- "referenceLocator": "pkg:npm/create-daloy@0.34.3"
30
+ "referenceLocator": "pkg:npm/create-daloy@0.35.1"
31
31
  }
32
32
  ]
33
33
  }
@@ -23,14 +23,43 @@ The `--with-ci` bundle adds these defaults:
23
23
  - If the generated project keeps the `Dockerfile`, container scanning lints it
24
24
  with hadolint, scans the source tree and built image with Trivy, and warns
25
25
  when `FROM` lines are not pinned to an immutable `@sha256:` digest.
26
- - CodeQL, OpenSSF Scorecard, zizmor, Dependabot for GitHub Actions and Docker
26
+ - CodeQL, Opengrep (second SAST engine, Aikido's LGPL-2.1 fork of
27
+ Semgrep, verified via sigstore cosign before execution), OpenSSF
28
+ Scorecard, zizmor, Dependabot for GitHub Actions and Docker
27
29
  base images, and CODEOWNERS are generated.
30
+ - A `secret-scan.yml` workflow runs [gitleaks](https://github.com/gitleaks/gitleaks) against the working tree on every PR / push and against the **full git history** on a daily schedule. The gitleaks binary is downloaded from a pinned official release and verified by SHA-256 before execution so the scan does not introduce a new third-party action into the supply chain. Findings block the merge; matched values are redacted from the public log. The rationale (and the remediation playbook for a confirmed leak) follows Aikido's [Secrets Detection guide](https://www.aikido.dev/blog/secret-detection-application-security): a secret in any commit, branch, or tag should be treated as compromised, and detection must consider the entire history — not just the latest snapshot — alongside GitHub-native push protection.
31
+
32
+ ## Cloud posture (operator checklist)
33
+
34
+ The framework cannot author your cloud configuration for you, but the
35
+ [Aikido "Top 7 Cloud Security Vulnerabilities"](https://www.aikido.dev/blog/top-cloud-security-vulnerabilities)
36
+ write-up maps cleanly onto a short checklist. Adopt these alongside the
37
+ container and CI defaults above:
38
+
39
+ - **IMDSv2 only.** On EC2 / equivalent, require token-based IMDSv2 and disable
40
+ IMDSv1. Combined with the framework's `fetchGuard()` on user-controlled
41
+ outbound fetches, this closes the Capital One 2019 / Pandoc CVE-2025-51591
42
+ SSRF → IAM chain.
43
+ - **Least-privilege execution role.** Scope the workload's IAM role to the
44
+ minimum actions and resource ARNs the handler actually calls (e.g.
45
+ `s3:GetObject` on a single bucket prefix, never `s3:*`).
46
+ - **Pod security on Kubernetes.** `runAsNonRoot: true`,
47
+ `readOnlyRootFilesystem: true`, `allowPrivilegeEscalation: false`,
48
+ `capabilities: { drop: ["ALL"] }`, and `automountServiceAccountToken: false`
49
+ on pods that do not call the kube API.
50
+ - **Network segmentation.** Keep dev / staging / prod in separate accounts /
51
+ projects. Apply default-deny `NetworkPolicy` or Security Group egress so a
52
+ compromised workload cannot freely reach the metadata service or the
53
+ database tier.
54
+ - **Log agent isolation.** Run FluentBit / Vector under its own ServiceAccount
55
+ with read-only access to the log path, pin the agent image to a digest, and
56
+ subscribe to its CVE feed.
28
57
 
29
58
  ## Required repository settings
30
59
 
31
60
  Before relying on these files for a company project:
32
61
 
33
62
  1. Replace `@your-org/security-team` in `.github/CODEOWNERS` or pass `--code-owner` when scaffolding.
34
- 2. Protect the `main` branch and require the CI, CodeQL, Scorecard, and zizmor checks.
63
+ 2. Protect the `main` branch and require the CI, CodeQL, Opengrep, Scorecard, and zizmor checks.
35
64
  3. Enable GitHub secret scanning and push protection.
36
65
  4. Keep Deno permissions narrow; do not switch tasks to `--allow-all`.
@@ -6,6 +6,8 @@
6
6
  # Workflow / CI / CD.
7
7
  /.github/ __CODE_OWNER__
8
8
  /.github/workflows/ __CODE_OWNER__
9
+ /.github/workflows/osv-scan.yml __CODE_OWNER__
10
+ /.github/workflows/secret-scan.yml __CODE_OWNER__
9
11
  /.github/dependabot.yml __CODE_OWNER__
10
12
  /.github/CODEOWNERS __CODE_OWNER__
11
13
 
@@ -1,12 +1,20 @@
1
- # Container security scan generated by create-daloy --with-ci.
1
+ # Container + IaC security scan generated by create-daloy --with-ci.
2
2
  #
3
- # The Deno template has no npm package manager, but its Dockerfile still
4
- # deserves the same container review as Node-based scaffolds:
3
+ # The Deno template has no npm package manager, but its Dockerfile and any
4
+ # IaC files (Terraform / Kubernetes / Helm / CloudFormation) the project
5
+ # adds still deserve the same review as Node-based scaffolds. Modeled on
6
+ # Aikido's "IaC security scanning for Terraform & Kubernetes
7
+ # misconfigurations" article
8
+ # (https://www.aikido.dev/blog/iac-security-scanning-terraform-kubernetes-misconfigurations):
5
9
  #
6
10
  # - hadolint lints the Dockerfile for known anti-patterns and unsafe
7
11
  # instructions (CIS Docker Benchmark coverage).
8
- # - Trivy scans the source tree (config + secrets + vulnerable lockfile
9
- # entries), then scans the built image for OS + language CVEs.
12
+ # - Trivy filesystem scan with the `misconfig` scanner enabled catches
13
+ # IaC misconfigurations (Terraform, Kubernetes, Helm, Dockerfile,
14
+ # CloudFormation) plus secrets and vulnerable lockfile entries. It
15
+ # runs even when no Dockerfile is present, so a project that only
16
+ # ships Terraform / K8s still gets the Aikido-style posture review.
17
+ # - Trivy image scan covers OS + language CVEs once the image is built.
10
18
  # - SARIF results are uploaded to the Code Scanning tab so findings show
11
19
  # up in the same place as CodeQL.
12
20
 
@@ -19,6 +27,17 @@ on:
19
27
  - ".dockerignore"
20
28
  - "deno.json"
21
29
  - "deno.lock"
30
+ - "**/*.tf"
31
+ - "**/*.tfvars"
32
+ - "**/*.hcl"
33
+ - "k8s/**"
34
+ - "helm/**"
35
+ - "**/deployment.yaml"
36
+ - "**/deployment.yml"
37
+ - "**/kustomization.yaml"
38
+ - "**/kustomization.yml"
39
+ - "**/Chart.yaml"
40
+ - "**/values.yaml"
22
41
  - ".github/workflows/container-scan.yml"
23
42
  push:
24
43
  branches: [main]
@@ -27,6 +46,17 @@ on:
27
46
  - ".dockerignore"
28
47
  - "deno.json"
29
48
  - "deno.lock"
49
+ - "**/*.tf"
50
+ - "**/*.tfvars"
51
+ - "**/*.hcl"
52
+ - "k8s/**"
53
+ - "helm/**"
54
+ - "**/deployment.yaml"
55
+ - "**/deployment.yml"
56
+ - "**/kustomization.yaml"
57
+ - "**/kustomization.yml"
58
+ - "**/Chart.yaml"
59
+ - "**/values.yaml"
30
60
  - ".github/workflows/container-scan.yml"
31
61
  schedule:
32
62
  # Weekly run catches newly-disclosed base-image CVEs even when the
@@ -114,12 +144,17 @@ jobs:
114
144
  sarif_file: hadolint.sarif
115
145
  category: hadolint
116
146
 
117
- - name: Trivy filesystem scan (config + secrets + vulns)
118
- if: steps.detect.outputs.present == 'true'
147
+ - name: Trivy filesystem scan (IaC misconfig + secrets + vulns)
148
+ # `misconfig` is the IaC posture scanner — Terraform, Kubernetes,
149
+ # Helm, Dockerfile, CloudFormation — per Aikido's "IaC security
150
+ # scanning for Terraform & Kubernetes misconfigurations" article.
151
+ # It runs unconditionally so a project that only ships Terraform
152
+ # or K8s (no Dockerfile) still gets reviewed on every PR.
119
153
  uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
120
154
  with:
121
155
  scan-type: fs
122
156
  scan-ref: .
157
+ scanners: vuln,secret,misconfig
123
158
  severity: HIGH,CRITICAL
124
159
  ignore-unfixed: true
125
160
  format: sarif
@@ -127,7 +162,6 @@ jobs:
127
162
  exit-code: "0"
128
163
 
129
164
  - name: Upload Trivy filesystem SARIF
130
- if: steps.detect.outputs.present == 'true'
131
165
  uses: github/codeql-action/upload-sarif@52485aec7be33610227643b0fe83936b8b5f061a # v3
132
166
  with:
133
167
  sarif_file: trivy-fs.sarif
@@ -9,11 +9,18 @@
9
9
  # After the image is published, point your platform at GHCR or replace the
10
10
  # final push steps with your provider's deploy command.
11
11
  #
12
+ # Every pushed image is also signed and attested with Sigstore Cosign
13
+ # (keyless / OIDC) and an SPDX SBOM, so consumers can verify provenance
14
+ # with `cosign verify` and `cosign verify-attestation --type spdxjson`
15
+ # instead of trusting the registry alone. See Aikido's "Container
16
+ # Security Best Practices" (https://www.aikido.dev/blog/container-security-best-practices)
17
+ # for why signed images + SBOM attestation are the supply-chain floor.
18
+ #
12
19
  # Optional container-host handoff examples:
13
20
  # - Fly.io: install flyctl in a later step and run
14
- # flyctl deploy --image "$IMAGE_REPO:sha-${GITHUB_SHA}" --remote-only
21
+ # flyctl deploy --image "$IMAGE_REPO@${IMAGE_DIGEST}" --remote-only
15
22
  # - Render: create an image-backed service that tracks
16
- # ghcr.io/<owner>/<repo>:sha-${GITHUB_SHA}
23
+ # ghcr.io/<owner>/<repo>@${IMAGE_DIGEST}
17
24
 
18
25
  name: Deploy
19
26
 
@@ -71,7 +78,12 @@ jobs:
71
78
  name: production
72
79
  permissions:
73
80
  contents: read
81
+ # `packages: write` lets us push to GHCR; `id-token: write` lets
82
+ # Cosign mint a short-lived OIDC identity for keyless image signing
83
+ # + SBOM attestation. Both are scoped to this single job — the
84
+ # top-level workflow still has `permissions: {}`.
74
85
  packages: write
86
+ id-token: write
75
87
 
76
88
  steps:
77
89
  - name: Harden runner
@@ -115,4 +127,57 @@ jobs:
115
127
  run: |
116
128
  set -eu
117
129
  docker push "$IMAGE_REPO:sha-${GITHUB_SHA}"
118
- docker push "$IMAGE_REPO:latest"
130
+ docker push "$IMAGE_REPO:latest"
131
+
132
+ - name: Resolve pushed image digest
133
+ # Resolve and pin the immutable digest the rest of the workflow
134
+ # signs and attests against. Signing a mutable tag would let any
135
+ # later push silently re-point a "verified" tag at attacker
136
+ # content; binding cosign + the SBOM attestation to
137
+ # ${IMAGE_REPO}@sha256:<digest> closes that race.
138
+ run: |
139
+ set -eu
140
+ digest="$(docker inspect --format='{{index .RepoDigests 0}}' "$IMAGE_REPO:sha-${GITHUB_SHA}" | awk -F@ '{print $2}')"
141
+ if [ -z "$digest" ]; then
142
+ echo "::error::Failed to resolve image digest for $IMAGE_REPO:sha-${GITHUB_SHA}" >&2
143
+ exit 1
144
+ fi
145
+ echo "IMAGE_DIGEST=$digest" >> "$GITHUB_ENV"
146
+ echo "IMAGE_REF=$IMAGE_REPO@$digest" >> "$GITHUB_ENV"
147
+ echo "::notice::Signed image reference: $IMAGE_REPO@$digest"
148
+
149
+ - name: Install Cosign
150
+ # SHA-pinned per the project's actions-pinning policy. v4.1.2,
151
+ # commit 6f9f17788090df1f26f669e9d70d6ae9567deba6.
152
+ uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
153
+
154
+ - name: Sign image with Cosign (keyless / OIDC)
155
+ # Keyless signing uses the workflow's `id-token` OIDC identity
156
+ # — no long-lived signing key to leak. Verifiers can pin to
157
+ # `--certificate-identity` matching this workflow URL.
158
+ env:
159
+ COSIGN_EXPERIMENTAL: "1"
160
+ run: |
161
+ set -eu
162
+ cosign sign --yes "$IMAGE_REF"
163
+
164
+ - name: Generate SPDX SBOM for image
165
+ # SHA-pinned per the project's actions-pinning policy. v0.24.0,
166
+ # commit e22c389904149dbc22b58101806040fa8d37a610.
167
+ uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
168
+ with:
169
+ image: ${{ env.IMAGE_REF }}
170
+ format: spdx-json
171
+ output-file: sbom.spdx.json
172
+ upload-artifact: false
173
+ upload-release-assets: false
174
+
175
+ - name: Attest SBOM with Cosign
176
+ env:
177
+ COSIGN_EXPERIMENTAL: "1"
178
+ run: |
179
+ set -eu
180
+ cosign attest --yes \
181
+ --predicate sbom.spdx.json \
182
+ --type spdxjson \
183
+ "$IMAGE_REF"