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.
Files changed (36) hide show
  1. package/README.md +49 -6
  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
@@ -0,0 +1,169 @@
1
+ # Opengrep — second SAST engine, complementing CodeQL.
2
+ #
3
+ # Why this exists in your scaffolded Daloy project:
4
+ # Daloy itself runs two SAST engines (CodeQL + Opengrep) because no
5
+ # single rule corpus catches everything. Opengrep is the LGPL-2.1
6
+ # community fork of Semgrep, ships Semgrep-compatible rules, and is
7
+ # backed by an open consortium. See Aikido's "Ultimate SAST Guide"
8
+ # (https://www.aikido.dev/blog/ultimate-sast-guide-static-application-security-testing)
9
+ # and "Launching Opengrep" (https://www.aikido.dev/blog/launching-opengrep-why-we-forked-semgrep).
10
+ #
11
+ # Pairing CodeQL (semantic, dataflow-heavy, language-server-grade)
12
+ # with Opengrep (pattern-based AST matching, fast, community-maintained
13
+ # rule packs) gives defense-in-depth at the source layer — the same
14
+ # posture Daloy maintains on its own repository.
15
+ #
16
+ # Hardening (matches the rest of the scaffolded bundle):
17
+ # * Top-level `permissions: {}`; the job opts in only to the two
18
+ # scopes it needs (`security-events: write` for SARIF upload,
19
+ # `contents: read` for checkout).
20
+ # * Third-party actions are SHA-pinned with version comments.
21
+ # * Opengrep itself is NOT installed via a third-party action.
22
+ # Instead the binary is downloaded from a pinned GitHub release
23
+ # and its sigstore cosign signature is verified against the
24
+ # official `opengrep/opengrep` release identity before execution.
25
+ # This avoids `curl ... | sh` and avoids adding a new pinned
26
+ # action to your supply chain just for this scan.
27
+ # * `step-security/harden-runner` runs in audit mode — the job
28
+ # needs egress to `github.com` (release artifact),
29
+ # `objects.githubusercontent.com` (release CDN), `semgrep.dev`
30
+ # (Semgrep-registry rule packs), and `rekor.sigstore.dev` /
31
+ # `fulcio.sigstore.dev` (cosign keyless verification). Audit
32
+ # logs every connection for post-hoc review.
33
+ # * `actions/checkout` runs with `persist-credentials: false`.
34
+ # * `set -euo pipefail` on every shell step; binaries are pinned
35
+ # by version + cosign signature.
36
+
37
+ name: Opengrep
38
+
39
+ on:
40
+ push:
41
+ branches: [main]
42
+ pull_request:
43
+ branches: [main]
44
+ schedule:
45
+ # 51 5 * * 4 — weekly Thursday, offset from CodeQL (Mon 04:37)
46
+ # and Scorecard so concurrent SARIF uploads do not collide.
47
+ - cron: "51 5 * * 4"
48
+ workflow_dispatch:
49
+
50
+ permissions: {}
51
+
52
+ concurrency:
53
+ group: opengrep-${{ github.workflow }}-${{ github.ref }}
54
+ cancel-in-progress: true
55
+
56
+ jobs:
57
+ scan:
58
+ name: Opengrep SAST
59
+ runs-on: ubuntu-latest
60
+ timeout-minutes: 20
61
+ permissions:
62
+ security-events: write
63
+ contents: read
64
+
65
+ env:
66
+ # Pin the Opengrep release we install. Bump this in lockstep
67
+ # with a fresh review of the upstream release notes; the
68
+ # signature verification step below provides a second line of
69
+ # defense if this URL is ever poisoned.
70
+ OPENGREP_VERSION: "v1.22.0"
71
+ OPENGREP_ASSET: "opengrep_manylinux_x86"
72
+
73
+ steps:
74
+ - name: Harden runner
75
+ uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2
76
+ with:
77
+ egress-policy: audit
78
+ disable-sudo: true
79
+
80
+ - name: Checkout
81
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
82
+ with:
83
+ persist-credentials: false
84
+ show-progress: false
85
+
86
+ - name: Download Opengrep release binary + signature
87
+ # We download the binary and its sigstore cert/sig directly
88
+ # from the pinned GitHub release. No `curl ... | sh`.
89
+ run: |
90
+ set -euo pipefail
91
+ mkdir -p .opengrep
92
+ base="https://github.com/opengrep/opengrep/releases/download/${OPENGREP_VERSION}/${OPENGREP_ASSET}"
93
+ echo "Fetching ${base}"
94
+ curl --proto '=https' --tlsv1.2 -fsSL --retry 3 --retry-delay 2 \
95
+ -o .opengrep/opengrep "${base}"
96
+ curl --proto '=https' --tlsv1.2 -fsSL --retry 3 --retry-delay 2 \
97
+ -o .opengrep/opengrep.cert "${base}.cert"
98
+ curl --proto '=https' --tlsv1.2 -fsSL --retry 3 --retry-delay 2 \
99
+ -o .opengrep/opengrep.sig "${base}.sig"
100
+ chmod +x .opengrep/opengrep
101
+
102
+ - name: Verify Opengrep signature (cosign / sigstore)
103
+ # `cosign` is preinstalled on GitHub-hosted `ubuntu-latest`
104
+ # runners. We require an identity issued by GitHub OIDC to
105
+ # the opengrep/opengrep release workflow; anything else is a
106
+ # hard fail (no `|| true`, no fallback).
107
+ run: |
108
+ set -euo pipefail
109
+ if ! command -v cosign >/dev/null 2>&1; then
110
+ echo "::error::cosign is required but not available on the runner."
111
+ exit 1
112
+ fi
113
+ cosign version
114
+ cosign verify-blob \
115
+ --cert ".opengrep/opengrep.cert" \
116
+ --signature ".opengrep/opengrep.sig" \
117
+ --certificate-identity-regexp "https://github.com/opengrep/opengrep.+" \
118
+ --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
119
+ ".opengrep/opengrep"
120
+ echo "Opengrep ${OPENGREP_VERSION} signature verified."
121
+
122
+ - name: Print Opengrep version
123
+ run: |
124
+ set -euo pipefail
125
+ .opengrep/opengrep --version
126
+
127
+ - name: Run Opengrep scan (SARIF)
128
+ # Curated security-focused rule packs from the Semgrep registry,
129
+ # which Opengrep consumes unchanged:
130
+ # * p/security-audit — generic SAST audit pack
131
+ # * p/owasp-top-ten — OWASP Top 10 application classes
132
+ # * p/cwe-top-25 — CWE Top 25 dangerous weaknesses
133
+ # * p/javascript / p/typescript — language-specific rules
134
+ # * p/nodejs — Node.js runtime-specific patterns
135
+ # * p/secrets — hardcoded credentials / API keys
136
+ #
137
+ # `--error` is intentionally OFF: SARIF is uploaded to GitHub
138
+ # code scanning and branch protection rules decide whether
139
+ # new findings gate merges. The workflow's exit status is
140
+ # reserved for *infrastructure* failures (download, signature,
141
+ # binary crash), not findings.
142
+ run: |
143
+ set -euo pipefail
144
+ .opengrep/opengrep scan \
145
+ --config p/security-audit \
146
+ --config p/owasp-top-ten \
147
+ --config p/cwe-top-25 \
148
+ --config p/javascript \
149
+ --config p/typescript \
150
+ --config p/nodejs \
151
+ --config p/secrets \
152
+ --sarif-output opengrep.sarif \
153
+ --metrics off \
154
+ --timeout 120 \
155
+ --max-target-bytes 1500000 \
156
+ --exclude node_modules \
157
+ --exclude dist \
158
+ --exclude build \
159
+ --exclude coverage \
160
+ --exclude .next \
161
+ --exclude .opengrep \
162
+ .
163
+
164
+ - name: Upload SARIF to GitHub code scanning
165
+ if: always()
166
+ uses: github/codeql-action/upload-sarif@52485aec7be33610227643b0fe83936b8b5f061a # v3
167
+ with:
168
+ sarif_file: opengrep.sarif
169
+ category: opengrep
@@ -0,0 +1,135 @@
1
+ # Second-source dependency scan against the OSV.dev database
2
+ # (Aikido "SAST vs SCA" / "npm-audit-guide — the missing layer" gate).
3
+ #
4
+ # Why this exists for a REST API service:
5
+ # `vuln-scan.yml` already runs `__AUDIT_PROD_STEP__`-equivalent
6
+ # `pnpm/npm/yarn/bun audit` against the committed lockfile every day,
7
+ # but that single feed (GHSA) is exactly the failure mode the Aikido
8
+ # write-up "SAST vs SCA" calls out: SCA findings are only as good as
9
+ # the database you query. The September 2025 npm phishing wave that
10
+ # compromised `debug`, `chalk`, and ~2B downloads/week of dependent
11
+ # packages (https://lwn.net/Articles/1037167/) showed that newly-
12
+ # disclosed malicious package versions often land in OSS feeds
13
+ # (OpenSSF `malicious-packages`, OSV.dev, PyPI, Go vuln DB) *before*
14
+ # they propagate into GHSA.
15
+ #
16
+ # This workflow adds a SECOND, independent source of vulnerability
17
+ # and malware intel: Google's OSV-Scanner, which queries OSV.dev and
18
+ # cross-references the OpenSSF `malicious-packages` corpus
19
+ # (https://github.com/ossf/malicious-packages). Findings that overlap
20
+ # with the daily package-manager audit are duplicates; findings that
21
+ # DO NOT overlap are the gap the Aikido article warns about.
22
+ #
23
+ # Defense-in-depth context (see SECURITY.md § Supply chain):
24
+ # * `.npmrc` `ignore-scripts=true` and `minimum-release-age=1440`
25
+ # (pnpm scaffolds) block the install-time and zero-day-window
26
+ # classes of supply-chain attack.
27
+ # * `verify-lockfile-sources.mjs` already refuses non-registry sources.
28
+ # * `pnpm/npm/yarn/bun audit` covers the GHSA feed.
29
+ # * This OSV pass is the LAST layer: "did a pinned version turn out
30
+ # to carry a CVE or a malware advisory after we pinned it?"
31
+ #
32
+ # Hardening:
33
+ # * `permissions: {}` at the top level; the single job opts in to
34
+ # `contents: read` only. No write permissions of any kind.
35
+ # * `step-security/harden-runner` in audit mode.
36
+ # * `actions/checkout` runs with `persist-credentials: false`.
37
+ # * The OSV-Scanner binary is downloaded directly from the official
38
+ # google/osv-scanner GitHub release and its SHA-256 is verified
39
+ # against a locally-pinned constant BEFORE the binary is executed.
40
+ # We deliberately avoid the third-party `google/osv-scanner-action`
41
+ # wrapper so the only third-party surface is the binary itself, not
42
+ # a layer of action-side YAML that could be retagged.
43
+ # * The scan is read-only and runs on the committed lockfile without
44
+ # installing any dependencies, so a malicious dev dep cannot run a
45
+ # postinstall during the scan itself.
46
+
47
+ name: OSV scan
48
+
49
+ on:
50
+ push:
51
+ branches: [main]
52
+ paths:
53
+ - "pnpm-lock.yaml"
54
+ - "package-lock.json"
55
+ - "npm-shrinkwrap.json"
56
+ - "yarn.lock"
57
+ - "bun.lock"
58
+ - "bun.lockb"
59
+ - "package.json"
60
+ - ".github/workflows/osv-scan.yml"
61
+ pull_request:
62
+ branches: [main]
63
+ paths:
64
+ - "pnpm-lock.yaml"
65
+ - "package-lock.json"
66
+ - "npm-shrinkwrap.json"
67
+ - "yarn.lock"
68
+ - "bun.lock"
69
+ - "bun.lockb"
70
+ - "package.json"
71
+ - ".github/workflows/osv-scan.yml"
72
+ schedule:
73
+ # 06:47 UTC daily — offset from vuln-scan (06:13) and Zizmor (07:00)
74
+ # so the SCA jobs do not collide.
75
+ - cron: "47 6 * * *"
76
+ workflow_dispatch:
77
+
78
+ permissions: {}
79
+
80
+ concurrency:
81
+ group: osv-scan-${{ github.workflow }}-${{ github.ref }}
82
+ cancel-in-progress: true
83
+
84
+ jobs:
85
+ scan:
86
+ name: OSV.dev + malicious-packages scan
87
+ runs-on: ubuntu-latest
88
+ timeout-minutes: 15
89
+ permissions:
90
+ contents: read
91
+
92
+ env:
93
+ # Pinned OSV-Scanner release. Bump together with the SHA-256 below.
94
+ # Source: https://github.com/google/osv-scanner/releases/tag/v2.3.8
95
+ OSV_SCANNER_VERSION: "2.3.8"
96
+ # SHA-256 of `osv-scanner_linux_amd64` from the v2.3.8
97
+ # `osv-scanner_SHA256SUMS` asset.
98
+ OSV_SCANNER_SHA256: "bc98e15319ed0d515e3f9235287ba53cdc5535d576d24fd573978ecfe9ab92dc"
99
+
100
+ steps:
101
+ - name: Harden runner
102
+ uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2
103
+ with:
104
+ egress-policy: audit
105
+ disable-sudo: true
106
+ disable-file-monitoring: false
107
+
108
+ - name: Checkout
109
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
110
+ with:
111
+ persist-credentials: false
112
+ show-progress: false
113
+
114
+ - name: Download and verify OSV-Scanner binary
115
+ # Pin the binary by SHA-256 BEFORE executing it. A registry or
116
+ # GitHub Releases compromise that swaps the asset would fail
117
+ # this check and abort the job.
118
+ run: |
119
+ set -euo pipefail
120
+ url="https://github.com/google/osv-scanner/releases/download/v${OSV_SCANNER_VERSION}/osv-scanner_linux_amd64"
121
+ curl --proto '=https' --tlsv1.2 --fail --silent --show-error --location \
122
+ --output osv-scanner "${url}"
123
+ echo "${OSV_SCANNER_SHA256} osv-scanner" | sha256sum --check --status
124
+ chmod +x osv-scanner
125
+ ./osv-scanner --version
126
+
127
+ - name: Scan lockfiles (OSV.dev + malicious-packages)
128
+ # `scan source --recursive` auto-detects whichever lockfile the
129
+ # scaffold actually ships (pnpm-lock.yaml, package-lock.json,
130
+ # yarn.lock, bun.lock, ...). Exits non-zero if any vulnerability
131
+ # or malware advisory matches a pinned version. This is the
132
+ # second-source gate beyond the package manager's own audit.
133
+ run: |
134
+ set -euo pipefail
135
+ ./osv-scanner scan source --recursive --format=table .
@@ -0,0 +1,106 @@
1
+ # Secret scanning — gitleaks against PRs, pushes to main, and a daily
2
+ # scheduled sweep of the full git history.
3
+ #
4
+ # Why this exists (Aikido "Secrets Detection: A Practical Guide"
5
+ # https://www.aikido.dev/blog/secret-detection-application-security):
6
+ #
7
+ # GitHub-native push protection only flags provider-shaped secrets at
8
+ # push time. Aikido's guide argues that effective secret detection must
9
+ # also scan the **full git history** (not just the latest snapshot) on
10
+ # a regular cadence, because a single leaked credential anywhere in any
11
+ # commit, branch, or tag should be treated as compromised.
12
+ #
13
+ # This workflow runs `gitleaks` on every PR / push against the working
14
+ # tree and, on a daily schedule, sweeps the entire commit history so
15
+ # long-lived secrets that pre-date push-protection enablement (or
16
+ # secrets that slipped past it via rebased / squashed fork PRs) are
17
+ # still surfaced.
18
+ #
19
+ # Hardening (matches the rest of the scaffolded CI bundle):
20
+ #
21
+ # * No new third-party GitHub Action: the gitleaks binary is fetched
22
+ # from the pinned official release and its SHA-256 is verified
23
+ # before execution.
24
+ # * Top-level `permissions: {}`; the single job opts in to
25
+ # `contents: read`.
26
+ # * `step-security/harden-runner` in audit mode, sudo disabled.
27
+ # * `actions/checkout` with `persist-credentials: false`.
28
+ # * `--redact` keeps any matched value out of the public log.
29
+ #
30
+ # When a finding lands:
31
+ # 1. Rotate the credential immediately.
32
+ # 2. Assess scope (what did the secret access?).
33
+ # 3. Remove it from history if your workflow allows (filter-repo /
34
+ # BFG); otherwise treat the repo + every downstream clone as
35
+ # compromised.
36
+ # 4. Add a `.gitleaksignore` entry only for confirmed false positives.
37
+
38
+ name: Secret scan
39
+
40
+ on:
41
+ pull_request:
42
+ push:
43
+ branches: [main]
44
+ schedule:
45
+ - cron: "21 4 * * *"
46
+ workflow_dispatch:
47
+
48
+ permissions: {}
49
+
50
+ concurrency:
51
+ group: secret-scan-${{ github.workflow }}-${{ github.ref }}
52
+ cancel-in-progress: true
53
+
54
+ jobs:
55
+ gitleaks:
56
+ name: gitleaks
57
+ runs-on: ubuntu-latest
58
+ timeout-minutes: 10
59
+ permissions:
60
+ contents: read
61
+
62
+ env:
63
+ # Pinned gitleaks release — bump in lockstep with the SHA-256 below.
64
+ # Source of truth: https://github.com/gitleaks/gitleaks/releases
65
+ GITLEAKS_VERSION: "8.30.1"
66
+ GITLEAKS_SHA256: "551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb"
67
+
68
+ steps:
69
+ - name: Harden runner
70
+ uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2
71
+ with:
72
+ egress-policy: audit
73
+ disable-sudo: true
74
+ disable-file-monitoring: false
75
+
76
+ - name: Checkout
77
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
78
+ with:
79
+ persist-credentials: false
80
+ show-progress: false
81
+ # Full history for the scheduled sweep so gitleaks can walk
82
+ # every commit; for PR / push runs a shallow clone is fine
83
+ # because the checked-out tree is what gitleaks scans.
84
+ fetch-depth: ${{ github.event_name == 'schedule' && 0 || 1 }}
85
+
86
+ - name: Install gitleaks (verified SHA-256)
87
+ run: |
88
+ set -euo pipefail
89
+ archive="gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
90
+ url="https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/${archive}"
91
+ curl --proto '=https' --tlsv1.2 -sSfL "$url" -o "$archive"
92
+ echo "${GITLEAKS_SHA256} ${archive}" | sha256sum --check --strict
93
+ mkdir -p "$RUNNER_TEMP/gitleaks"
94
+ tar -xzf "$archive" -C "$RUNNER_TEMP/gitleaks" gitleaks
95
+ rm -f "$archive"
96
+ echo "$RUNNER_TEMP/gitleaks" >> "$GITHUB_PATH"
97
+
98
+ - name: gitleaks version
99
+ run: gitleaks version
100
+
101
+ - name: gitleaks (working tree)
102
+ run: gitleaks dir --no-banner --redact --verbose --exit-code 1 .
103
+
104
+ - name: gitleaks (full git history)
105
+ if: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }}
106
+ run: gitleaks git --no-banner --redact --verbose --exit-code 1 .
@@ -41,6 +41,18 @@ Do not write `.js` here — that's the Node NodeNext convention and will fail to
41
41
  6. Every new route ships with a test that covers a happy path and at least one unhappy path.
42
42
  7. After any route change: `bun run gen:openapi && bun run gen:client && bun run typecheck && bun test`.
43
43
 
44
+ ## Secure-by-default (do not let an AI strip these)
45
+
46
+ Per Supabase + Aikido on [secure-by-default development](https://www.aikido.dev/blog/supabase-approach-to-secure-by-default-development): *"If you tell an AI to make something work, it might remove the very security checks that protect you."* When a guard rejects a request, **satisfy it, do not delete it.**
47
+
48
+ - Keep `secureHeaders()`, `requestId()`, `rateLimit()` registered, and `bodyLimitBytes` / `requestTimeoutMs` set on `new App({...})`. Tighten per-route; never raise globally to pass a test.
49
+ - Keep Zod `.strict()` on top-level request objects; do not switch to `.passthrough()`. Keep `responses[N].body` schemas tight; never widen to `z.any()` to let a privileged field escape.
50
+ - Every protected route attaches an auth `beforeHandle` and ships an unhappy-path test proving an unauthenticated request returns `401` (and wrong scope returns `403`) — the HTTP-boundary equivalent of Supabase's pgTAP policy tests.
51
+ - JWT verifiers keep an explicit `algorithms` allowlist; never trust the token's `alg` header, never allow `none`, always check `exp` / `nbf`.
52
+ - Credential / HMAC comparisons use `timingSafeEqual`, never `===`. Throw typed errors from `@daloyjs/core` so problem+json redacts in prod; never return raw stack traces.
53
+ - `.env`, secrets, and private keys never get committed — the template `_gitignore` is the source of truth.
54
+ - Do not bypass safety checks (`--no-verify`, `bun install --trust`) without recording the reason in the PR.
55
+
44
56
  ## Process expectations
45
57
 
46
58
  - Quality gates must pass before declaring work done: `bun run typecheck` and `bun test`.
@@ -8,3 +8,8 @@ coverage/
8
8
  !.env.example
9
9
  generated/
10
10
  bun.lockb
11
+
12
+ # An `npm-shrinkwrap.json` published with a package overrides the
13
+ # consumer's lockfile and can pin transitives to unsigned tarballs.
14
+ # See https://socket.dev/blog/understanding-the-security-concerns-of-npm-shrinkwrap
15
+ npm-shrinkwrap.json
@@ -13,10 +13,11 @@
13
13
  "test": "bun test",
14
14
  "gen:openapi": "bun run scripts/dump-openapi.ts",
15
15
  "gen:client": "openapi-ts",
16
- "gen": "pnpm gen:openapi && pnpm gen:client"
16
+ "gen": "pnpm gen:openapi && pnpm gen:client",
17
+ "audit": "pnpm audit --prod"
17
18
  },
18
19
  "dependencies": {
19
- "@daloyjs/core": "^0.34.3",
20
+ "@daloyjs/core": "^0.35.0",
20
21
  "zod": "^4.4.3"
21
22
  },
22
23
  "devDependencies": {
@@ -31,6 +31,19 @@ A [DaloyJS](https://daloyjs.dev) REST API deployed to **Cloudflare Workers**. **
31
31
  8. Long-running work belongs in `ctx.waitUntil(...)`, not blocking the response.
32
32
  9. Every new route ships with a test that covers a happy path and at least one unhappy path.
33
33
 
34
+ ## Secure-by-default (do not let an AI strip these)
35
+
36
+ Per Supabase + Aikido on [secure-by-default development](https://www.aikido.dev/blog/supabase-approach-to-secure-by-default-development): *"If you tell an AI to make something work, it might remove the very security checks that protect you."* When a guard rejects a request, **satisfy it, do not delete it.**
37
+
38
+ - Keep `secureHeaders()`, `requestId()`, `rateLimit()` registered, and `bodyLimitBytes` / `requestTimeoutMs` set on `new App({...})`. For production, add Cloudflare's native rate-limit binding **in addition to** the in-memory limiter, not instead of it.
39
+ - Read secrets and bindings (KV, D1, R2) from the `env` argument; never hard-code, never log them.
40
+ - Keep Zod `.strict()` on top-level request objects; do not switch to `.passthrough()`. Keep `responses[N].body` schemas tight; never widen to `z.any()` to let a privileged field escape.
41
+ - Every protected route attaches an auth `beforeHandle` and ships an unhappy-path test proving an unauthenticated request returns `401` (and wrong scope returns `403`) — the HTTP-boundary equivalent of Supabase's pgTAP policy tests.
42
+ - JWT verifiers keep an explicit `algorithms` allowlist; never trust the token's `alg` header, never allow `none`, always check `exp` / `nbf`.
43
+ - Credential / HMAC comparisons use `crypto.subtle.timingSafeEqual`, never `===`. Throw typed errors from `@daloyjs/core` so problem+json redacts in prod; never return raw stack traces.
44
+ - Keep `compatibility_date` pinned; do not enable `nodejs_compat` unless a feature requires it.
45
+ - `.env`, `.dev.vars`, secrets, private keys: never commit. Use `wrangler secret put` for production secrets.
46
+
34
47
  ## Process expectations
35
48
 
36
49
  - Quality gates must pass before declaring work done: `pnpm typecheck` and `pnpm test`.
@@ -4,4 +4,13 @@ dist/
4
4
  .DS_Store
5
5
  *.log
6
6
  .env
7
+ .env.*
8
+ !.env.example
7
9
  .dev.vars
10
+ .dev.vars.*
11
+ !.dev.vars.example
12
+
13
+ # An `npm-shrinkwrap.json` published with a package overrides the
14
+ # consumer's lockfile and can pin transitives to unsigned tarballs.
15
+ # See https://socket.dev/blog/understanding-the-security-concerns-of-npm-shrinkwrap
16
+ npm-shrinkwrap.json
@@ -12,5 +12,6 @@ minimum-release-age=1440
12
12
 
13
13
  # Block postinstall/preinstall/prepare hooks from transitive deps —
14
14
  # the primary execution channel for chalk/debug, node-ipc, Shai-Hulud.
15
- # Allowlist trusted builds via package.json `pnpm.onlyBuiltDependencies`.
15
+ # If you later adopt pnpm's build-script allowlist, keep it in
16
+ # pnpm-workspace.yaml instead of turning this off.
16
17
  ignore-scripts=true
@@ -7,10 +7,11 @@
7
7
  "dev": "wrangler dev",
8
8
  "deploy": "wrangler deploy",
9
9
  "typecheck": "tsc --noEmit",
10
- "test": "node --import tsx/esm --test tests/**/*.test.ts"
10
+ "test": "node --import tsx/esm --test tests/**/*.test.ts",
11
+ "audit": "pnpm audit --prod"
11
12
  },
12
13
  "dependencies": {
13
- "@daloyjs/core": "^0.34.3",
14
+ "@daloyjs/core": "^0.35.0",
14
15
  "zod": "^4.4.3"
15
16
  },
16
17
  "devDependencies": {
@@ -33,6 +33,18 @@ The typed Hey API SDK is generated outside Deno (Hey API has no Deno entrypoint
33
33
  7. Every new route ships with a test that covers a happy path and at least one unhappy path.
34
34
  8. After any route change: `deno task gen:openapi && deno task typecheck && deno task test`.
35
35
 
36
+ ## Secure-by-default (do not let an AI strip these)
37
+
38
+ Per Supabase + Aikido on [secure-by-default development](https://www.aikido.dev/blog/supabase-approach-to-secure-by-default-development): *"If you tell an AI to make something work, it might remove the very security checks that protect you."* When a guard rejects a request, **satisfy it, do not delete it.**
39
+
40
+ - Keep `secureHeaders()`, `requestId()`, `rateLimit()` registered, and `bodyLimitBytes` / `requestTimeoutMs` set on `new App({...})`. Tighten per-route; never raise globally to pass a test.
41
+ - Keep Deno permissions narrow. Never add `--allow-all`; never broaden `--allow-net` / `--allow-read` / `--allow-env` to silence a prompt — add the specific host / path / var.
42
+ - Keep Zod `.strict()` on top-level request objects; do not switch to `.passthrough()`. Keep `responses[N].body` schemas tight; never widen to `z.any()` to let a privileged field escape.
43
+ - Every protected route attaches an auth `beforeHandle` and ships an unhappy-path test proving an unauthenticated request returns `401` (and wrong scope returns `403`) — the HTTP-boundary equivalent of Supabase's pgTAP policy tests.
44
+ - JWT verifiers keep an explicit `algorithms` allowlist; never trust the token's `alg` header, never allow `none`, always check `exp` / `nbf`.
45
+ - Credential / HMAC comparisons use a constant-time comparison, never `===`. Throw typed errors from `@daloyjs/core` so problem+json redacts in prod; never return raw stack traces.
46
+ - `.env`, secrets, and private keys never get committed — the template `_gitignore` is the source of truth.
47
+
36
48
  ## Process expectations
37
49
 
38
50
  - Quality gates must pass before declaring work done: `deno task typecheck` and `deno task test`.
@@ -5,3 +5,8 @@ node_modules/
5
5
  .env.*
6
6
  !.env.example
7
7
  generated/
8
+
9
+ # An `npm-shrinkwrap.json` published with a package overrides the
10
+ # consumer's lockfile and can pin transitives to unsigned tarballs.
11
+ # See https://socket.dev/blog/understanding-the-security-concerns-of-npm-shrinkwrap
12
+ npm-shrinkwrap.json
@@ -8,8 +8,8 @@
8
8
  "gen:openapi": "deno run --allow-net --allow-env --allow-read --allow-write scripts/dump-openapi.ts"
9
9
  },
10
10
  "imports": {
11
- "@daloyjs/core": "npm:@daloyjs/core@^0.34.3",
12
- "@daloyjs/core/": "npm:@daloyjs/core@^0.34.3/",
11
+ "@daloyjs/core": "npm:@daloyjs/core@^0.35.0",
12
+ "@daloyjs/core/": "npm:@daloyjs/core@^0.35.0/",
13
13
  "zod": "npm:zod@^4.4.3"
14
14
  },
15
15
  "compilerOptions": {
@@ -45,6 +45,18 @@ This is the official Node.js ESM convention — TypeScript rewrites the specifie
45
45
  6. Every new route ships with a test that covers a happy path and at least one unhappy path.
46
46
  7. After any route change: `pnpm gen && pnpm typecheck && pnpm test`.
47
47
 
48
+ ## Secure-by-default (do not let an AI strip these)
49
+
50
+ Per Supabase + Aikido on [secure-by-default development](https://www.aikido.dev/blog/supabase-approach-to-secure-by-default-development): *"If you tell an AI to make something work, it might remove the very security checks that protect you."* When a guard rejects a request, **satisfy it, do not delete it.**
51
+
52
+ - Keep `secureHeaders()`, `requestId()`, `rateLimit()` registered, and `bodyLimitBytes` / `requestTimeoutMs` set on `new App({...})`. Tighten per-route; never raise globally to pass a test.
53
+ - Keep Zod `.strict()` on top-level request objects; do not switch to `.passthrough()`. Keep `responses[N].body` schemas tight; never widen to `z.any()` to let a privileged field escape.
54
+ - Every protected route attaches an auth `beforeHandle` and ships an unhappy-path test proving an unauthenticated request returns `401` (and wrong scope returns `403`) — the HTTP-boundary equivalent of Supabase's pgTAP policy tests.
55
+ - JWT verifiers keep an explicit `algorithms` allowlist; never trust the token's `alg` header, never allow `none`, always check `exp` / `nbf`.
56
+ - Credential / HMAC comparisons use `timingSafeEqual`, never `===`. Throw typed errors from `@daloyjs/core` so problem+json redacts in prod; never return raw stack traces.
57
+ - `.env`, secrets, and private keys never get committed — the template `_gitignore` is the source of truth.
58
+ - Do not bypass safety checks (`--no-verify`, `--ignore-scripts=false`, lowering the 24h `minimum-release-age` in `.npmrc`) without recording the reason in the PR.
59
+
48
60
  ## Process expectations
49
61
 
50
62
  - Quality gates must pass before declaring work done: `pnpm typecheck` and `pnpm test`.
@@ -7,3 +7,8 @@ coverage/
7
7
  .env.*
8
8
  !.env.example
9
9
  generated/
10
+
11
+ # An `npm-shrinkwrap.json` published with a package overrides the
12
+ # consumer's lockfile and can pin transitives to unsigned tarballs.
13
+ # See https://socket.dev/blog/understanding-the-security-concerns-of-npm-shrinkwrap
14
+ npm-shrinkwrap.json
@@ -16,6 +16,6 @@ minimum-release-age=1440
16
16
 
17
17
  # postinstall / preinstall / prepare hooks from transitive deps are the
18
18
  # main execution channel used by chalk/debug, node-ipc, and Shai-Hulud
19
- # malware. Allowlist the few packages you actually trust to build via
20
- # package.json `pnpm.onlyBuiltDependencies` instead of turning this off.
19
+ # malware. If you later adopt pnpm's build-script allowlist, keep it in
20
+ # pnpm-workspace.yaml instead of turning this off.
21
21
  ignore-scripts=true
@@ -18,7 +18,7 @@
18
18
  "audit": "pnpm audit --prod"
19
19
  },
20
20
  "dependencies": {
21
- "@daloyjs/core": "^0.34.3",
21
+ "@daloyjs/core": "^0.35.0",
22
22
  "zod": "^4.4.3"
23
23
  },
24
24
  "devDependencies": {
@@ -40,6 +40,18 @@ This is the official Node.js ESM convention — TypeScript rewrites the specifie
40
40
  7. The catch-all `api/[...path].ts` must remain a catch-all so DaloyJS handles routing.
41
41
  8. Every new route ships with a test that covers a happy path and at least one unhappy path.
42
42
 
43
+ ## Secure-by-default (do not let an AI strip these)
44
+
45
+ Per Supabase + Aikido on [secure-by-default development](https://www.aikido.dev/blog/supabase-approach-to-secure-by-default-development): *"If you tell an AI to make something work, it might remove the very security checks that protect you."* When a guard rejects a request, **satisfy it, do not delete it.**
46
+
47
+ - Keep `secureHeaders()`, `requestId()`, `rateLimit()` registered, and `bodyLimitBytes` / `requestTimeoutMs` set on `new App({...})`. For production, back the limiter with Vercel KV **in addition to** the in-memory limiter (which resets per instance).
48
+ - Keep Zod `.strict()` on top-level request objects; do not switch to `.passthrough()`. Keep `responses[N].body` schemas tight; never widen to `z.any()` to let a privileged field escape.
49
+ - Every protected route attaches an auth `beforeHandle` and ships an unhappy-path test proving an unauthenticated request returns `401` (and wrong scope returns `403`) — the HTTP-boundary equivalent of Supabase's pgTAP policy tests.
50
+ - JWT verifiers keep an explicit `algorithms` allowlist; never trust the token's `alg` header, never allow `none`, always check `exp` / `nbf`.
51
+ - Credential / HMAC comparisons use `crypto.subtle.timingSafeEqual`, never `===`. Throw typed errors from `@daloyjs/core` so problem+json redacts in prod; never return raw stack traces.
52
+ - Keep `api/[...path].ts` a catch-all so DaloyJS owns routing — do not split into per-path files that bypass the middleware chain.
53
+ - `.env`, `.env.local`, secrets, private keys: never commit. Use `vercel env` for production secrets.
54
+
43
55
  ## Process expectations
44
56
 
45
57
  - Quality gates must pass before declaring work done: `pnpm typecheck` and `pnpm test`.
@@ -7,3 +7,8 @@ coverage/
7
7
  .env
8
8
  .env.*
9
9
  !.env.example
10
+
11
+ # An `npm-shrinkwrap.json` published with a package overrides the
12
+ # consumer's lockfile and can pin transitives to unsigned tarballs.
13
+ # See https://socket.dev/blog/understanding-the-security-concerns-of-npm-shrinkwrap
14
+ npm-shrinkwrap.json
@@ -12,5 +12,6 @@ minimum-release-age=1440
12
12
 
13
13
  # Block postinstall/preinstall/prepare hooks from transitive deps —
14
14
  # the primary execution channel for chalk/debug, node-ipc, Shai-Hulud.
15
- # Allowlist trusted builds via package.json `pnpm.onlyBuiltDependencies`.
15
+ # If you later adopt pnpm's build-script allowlist, keep it in
16
+ # pnpm-workspace.yaml instead of turning this off.
16
17
  ignore-scripts=true