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.
- package/README.md +74 -20
- package/bin/create-daloy.mjs +83 -17
- package/package.json +2 -2
- package/sbom.cdx.json +13 -9
- package/sbom.spdx.json +5 -5
- package/templates/_ci/deno/SECURITY.md +31 -2
- package/templates/_ci/deno/_github/CODEOWNERS +2 -0
- package/templates/_ci/deno/_github/workflows/container-scan.yml +42 -8
- package/templates/_ci/deno/_github/workflows/deploy.yml +68 -3
- package/templates/_ci/deno/_github/workflows/opengrep.yml +137 -0
- package/templates/_ci/deno/_github/workflows/osv-scan.yml +121 -0
- package/templates/_ci/deno/_github/workflows/secret-scan.yml +106 -0
- package/templates/_ci/node/SECURITY.md +100 -2
- package/templates/_ci/node/_github/CODEOWNERS +2 -0
- package/templates/_ci/node/_github/workflows/container-scan.yml +46 -10
- package/templates/_ci/node/_github/workflows/opengrep.yml +169 -0
- package/templates/_ci/node/_github/workflows/osv-scan.yml +135 -0
- package/templates/_ci/node/_github/workflows/secret-scan.yml +106 -0
- package/templates/bun-basic/AGENTS.md +12 -0
- package/templates/bun-basic/_gitignore +5 -0
- package/templates/bun-basic/package.json +3 -2
- package/templates/cloudflare-worker/AGENTS.md +13 -0
- package/templates/cloudflare-worker/_gitignore +9 -0
- package/templates/cloudflare-worker/_npmrc +2 -1
- package/templates/cloudflare-worker/package.json +3 -2
- package/templates/deno-basic/AGENTS.md +12 -0
- package/templates/deno-basic/_gitignore +5 -0
- package/templates/deno-basic/deno.json +2 -2
- package/templates/node-basic/AGENTS.md +12 -0
- package/templates/node-basic/_gitignore +5 -0
- package/templates/node-basic/_npmrc +2 -2
- package/templates/node-basic/package.json +1 -1
- package/templates/vercel-edge/AGENTS.md +12 -0
- package/templates/vercel-edge/_gitignore +5 -0
- package/templates/vercel-edge/_npmrc +2 -1
- 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
|
|
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
|
|
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
|
-
|
|
155
|
-
|
|
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`
|
|
158
|
-
templates publish a Docker image to
|
|
159
|
-
|
|
160
|
-
read
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
|
168
|
-
-
|
|
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
|
|
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,
|
|
179
|
-
|
|
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
|
|
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.
|
package/bin/create-daloy.mjs
CHANGED
|
@@ -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 +
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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, "
|
|
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`
|
|
1617
|
-
//
|
|
1618
|
-
//
|
|
1619
|
-
//
|
|
1620
|
-
//
|
|
1621
|
-
//
|
|
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
|
|
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.
|
|
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:
|
|
4
|
+
"serialNumber": "urn:uuid:c11f2b19-2472-59a3-9312-536e039ae8be",
|
|
5
5
|
"version": 1,
|
|
6
6
|
"metadata": {
|
|
7
|
-
"timestamp": "2026-05-
|
|
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.
|
|
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.
|
|
18
|
+
"bom-ref": "pkg:npm/create-daloy@0.35.1",
|
|
19
19
|
"name": "create-daloy",
|
|
20
|
-
"version": "0.
|
|
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.
|
|
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.
|
|
45
|
+
"tagId": "swidtag-create-daloy-0.35.1",
|
|
42
46
|
"name": "create-daloy",
|
|
43
|
-
"version": "0.
|
|
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.
|
|
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.
|
|
6
|
-
"documentNamespace": "https://github.com/daloyjs/daloy/sbom/create-daloy-0.
|
|
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-
|
|
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.
|
|
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.
|
|
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,
|
|
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
|
|
4
|
-
#
|
|
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
|
|
9
|
-
#
|
|
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 (
|
|
118
|
-
|
|
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
|
|
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
|
|
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"
|