create-daloy 0.34.3 → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -6
- 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 (so first-time runs are not blocked by the 24h `minimumReleaseAge` embargo
|
|
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 and so you can review the scaffold's hardened `.npmrc` and `pnpm-workspace.yaml` before the first install). |
|
|
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
|
|
|
@@ -165,6 +164,40 @@ For Node-style templates, the bundle adds:
|
|
|
165
164
|
*after* the last PR or push and provides SOC 2 CC7.1
|
|
166
165
|
([continuous vulnerability management](https://www.aikido.dev/blog/a-guide-to-automating-technical-vulnerability-management-for-soc-2))
|
|
167
166
|
evidence even when developers are not touching the repo.
|
|
167
|
+
- `.github/workflows/osv-scan.yml` — a SECOND, independent SCA source.
|
|
168
|
+
`vuln-scan.yml` queries the package manager's audit feed (GHSA); this one
|
|
169
|
+
runs Google's OSV-Scanner against the committed lockfile and cross-references
|
|
170
|
+
the OpenSSF
|
|
171
|
+
[malicious-packages](https://github.com/ossf/malicious-packages) corpus, so
|
|
172
|
+
a malware advisory that lands in OSV.dev before it propagates to GHSA still
|
|
173
|
+
fails the build. The binary is downloaded from a pinned official release and
|
|
174
|
+
verified by SHA-256 before execution — no third-party action is added to the
|
|
175
|
+
supply chain just for this scan. This is the missing layer the Aikido
|
|
176
|
+
[SAST vs SCA](https://www.aikido.dev/blog/sast-vs-sca) and
|
|
177
|
+
[npm-audit-guide](https://www.aikido.dev/blog/npm-audit-guide) write-ups
|
|
178
|
+
warn about, and the Deno scaffold gets it too (Deno has no `audit` built
|
|
179
|
+
in, so without OSV-Scanner a Deno scaffold would have no scheduled SCA at
|
|
180
|
+
all).
|
|
181
|
+
- `.github/workflows/secret-scan.yml` — runs [gitleaks](https://github.com/gitleaks/gitleaks)
|
|
182
|
+
on every PR / push (working tree) and on a daily schedule across the **full
|
|
183
|
+
git history**, so a credential leaked anywhere in any commit, branch, or tag
|
|
184
|
+
is surfaced even if GitHub-native push protection missed it. The gitleaks
|
|
185
|
+
binary is downloaded from a pinned official release and verified by SHA-256
|
|
186
|
+
before execution — no third-party action is added to the supply chain just
|
|
187
|
+
for this scan. See Aikido's
|
|
188
|
+
[Secrets Detection guide](https://www.aikido.dev/blog/secret-detection-application-security)
|
|
189
|
+
for why history-aware scanning is the floor and not the ceiling.
|
|
190
|
+
- `.github/workflows/opengrep.yml` — a second SAST source alongside CodeQL,
|
|
191
|
+
using [Opengrep](https://github.com/opengrep/opengrep) (an open-source
|
|
192
|
+
Semgrep fork) with the same pinned-binary + SHA-256-verified pattern as the
|
|
193
|
+
OSV and gitleaks scans.
|
|
194
|
+
- `.github/workflows/container-scan.yml` — runs Trivy against the image
|
|
195
|
+
produced by the template's `_Dockerfile` (filesystem scan on PR, full image
|
|
196
|
+
scan on push to `main`) so a base-image CVE or a vulnerable layer is
|
|
197
|
+
surfaced before deploy.
|
|
198
|
+
- `.github/workflows/dast.yml` — a manual-only dynamic-analysis workflow that
|
|
199
|
+
boots the scaffolded API and runs an OWASP ZAP baseline scan against it,
|
|
200
|
+
for teams that want a black-box check before promoting a release.
|
|
168
201
|
- CodeQL, OpenSSF Scorecard, zizmor, Dependabot, CODEOWNERS, and `SECURITY.md`.
|
|
169
202
|
- `scripts/verify-lockfile-sources.mjs` plus a `verify:lockfile` package script
|
|
170
203
|
that rejects git dependencies and non-registry tarball URLs in text lockfiles.
|
|
@@ -175,8 +208,9 @@ out a reusable package, opt into npm trusted publishing yourself.
|
|
|
175
208
|
|
|
176
209
|
For `deno-basic`, `--with-ci` generates a Deno-native CI workflow, a manual-only
|
|
177
210
|
container publish starter for GHCR that is guarded to `main` or a tag by
|
|
178
|
-
default, plus CodeQL,
|
|
179
|
-
|
|
211
|
+
default, plus CodeQL, Opengrep, **OSV-Scanner** (the only scheduled SCA layer
|
|
212
|
+
a Deno scaffold has, since Deno ships no `audit`), Scorecard, zizmor,
|
|
213
|
+
Dependabot for GitHub Actions, CODEOWNERS, and `SECURITY.md`.
|
|
180
214
|
|
|
181
215
|
If you want the governance bundle but not the deployment starter, pass
|
|
182
216
|
`--with-ci --no-deploy`. If you only want a deployment starter, pass
|
|
@@ -187,12 +221,21 @@ If you omit `--code-owner`, the generated CODEOWNERS file uses
|
|
|
187
221
|
protection. You should also enable GitHub secret scanning, push protection, and
|
|
188
222
|
required status checks in the repository settings.
|
|
189
223
|
|
|
224
|
+
## Container-first scaffolds
|
|
225
|
+
|
|
226
|
+
Every template (Node, Bun, Vercel Edge, Cloudflare Worker, and Deno) ships a
|
|
227
|
+
production-oriented `Dockerfile` and `.dockerignore` with the secure-by-default
|
|
228
|
+
posture from `@daloyjs/core` `0.24.0`: a non-root user, `STOPSIGNAL SIGTERM`,
|
|
229
|
+
`tini` as PID 1, and a `HEALTHCHECK` pointed at `/readyz`. Node-style templates
|
|
230
|
+
also ship an `.env.example`. None of this is required — delete or replace
|
|
231
|
+
whatever you do not need.
|
|
232
|
+
|
|
190
233
|
## What the CLI guarantees
|
|
191
234
|
|
|
192
235
|
- Zero runtime dependencies (uses only Node built-ins) for a clean supply-chain footprint.
|
|
193
236
|
- A modern terminal experience with Unicode/color capability detection and ASCII fallbacks.
|
|
194
237
|
- 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
|
|
238
|
+
- 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
239
|
- pnpm-specific `.npmrc` hardening is kept only when you choose `pnpm`; other package managers get a clean project without unsupported config warnings.
|
|
197
240
|
- 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
241
|
- `--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.0",
|
|
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:94862215-9d79-5a0f-9ee1-81c52885d00f",
|
|
5
5
|
"version": 1,
|
|
6
6
|
"metadata": {
|
|
7
|
-
"timestamp": "2026-05-
|
|
7
|
+
"timestamp": "2026-05-24T23:40:28.395Z",
|
|
8
8
|
"tools": [
|
|
9
9
|
{
|
|
10
10
|
"vendor": "DaloyJS",
|
|
11
11
|
"name": "daloy-generate-sbom",
|
|
12
|
-
"version": "0.
|
|
12
|
+
"version": "0.35.0"
|
|
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.0",
|
|
19
19
|
"name": "create-daloy",
|
|
20
|
-
"version": "0.
|
|
20
|
+
"version": "0.35.0",
|
|
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.0",
|
|
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.0",
|
|
42
46
|
"name": "create-daloy",
|
|
43
|
-
"version": "0.
|
|
47
|
+
"version": "0.35.0",
|
|
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.0",
|
|
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.0",
|
|
6
|
+
"documentNamespace": "https://github.com/daloyjs/daloy/sbom/create-daloy-0.35.0-94862215-9d79-5a0f-9ee1-81c52885d00f",
|
|
7
7
|
"creationInfo": {
|
|
8
|
-
"created": "2026-05-
|
|
8
|
+
"created": "2026-05-24T23:40:28.395Z",
|
|
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.0",
|
|
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.0"
|
|
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"
|