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,137 @@
1
+ # Opengrep — second SAST engine, complementing CodeQL.
2
+ #
3
+ # Why this exists in your scaffolded Daloy (Deno) 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) with Opengrep
12
+ # (pattern-based AST matching, community rule packs) gives
13
+ # defense-in-depth at the source layer — the same posture Daloy
14
+ # 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`, `contents: read`).
19
+ # * Third-party actions are SHA-pinned with version comments.
20
+ # * Opengrep itself is NOT installed via a third-party action;
21
+ # the binary is downloaded from a pinned GitHub release and its
22
+ # sigstore cosign signature is verified before execution.
23
+ # * `step-security/harden-runner` runs in audit mode.
24
+ # * `actions/checkout` runs with `persist-credentials: false`.
25
+ # * `set -euo pipefail` on every shell step.
26
+
27
+ name: Opengrep
28
+
29
+ on:
30
+ push:
31
+ branches: [main]
32
+ pull_request:
33
+ branches: [main]
34
+ schedule:
35
+ - cron: "51 5 * * 4"
36
+ workflow_dispatch:
37
+
38
+ permissions: {}
39
+
40
+ concurrency:
41
+ group: opengrep-${{ github.workflow }}-${{ github.ref }}
42
+ cancel-in-progress: true
43
+
44
+ jobs:
45
+ scan:
46
+ name: Opengrep SAST
47
+ runs-on: ubuntu-latest
48
+ timeout-minutes: 20
49
+ permissions:
50
+ security-events: write
51
+ contents: read
52
+
53
+ env:
54
+ OPENGREP_VERSION: "v1.22.0"
55
+ OPENGREP_ASSET: "opengrep_manylinux_x86"
56
+
57
+ steps:
58
+ - name: Harden runner
59
+ uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2
60
+ with:
61
+ egress-policy: audit
62
+ disable-sudo: true
63
+
64
+ - name: Checkout
65
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
66
+ with:
67
+ persist-credentials: false
68
+ show-progress: false
69
+
70
+ - name: Download Opengrep release binary + signature
71
+ run: |
72
+ set -euo pipefail
73
+ mkdir -p .opengrep
74
+ base="https://github.com/opengrep/opengrep/releases/download/${OPENGREP_VERSION}/${OPENGREP_ASSET}"
75
+ echo "Fetching ${base}"
76
+ curl --proto '=https' --tlsv1.2 -fsSL --retry 3 --retry-delay 2 \
77
+ -o .opengrep/opengrep "${base}"
78
+ curl --proto '=https' --tlsv1.2 -fsSL --retry 3 --retry-delay 2 \
79
+ -o .opengrep/opengrep.cert "${base}.cert"
80
+ curl --proto '=https' --tlsv1.2 -fsSL --retry 3 --retry-delay 2 \
81
+ -o .opengrep/opengrep.sig "${base}.sig"
82
+ chmod +x .opengrep/opengrep
83
+
84
+ - name: Verify Opengrep signature (cosign / sigstore)
85
+ run: |
86
+ set -euo pipefail
87
+ if ! command -v cosign >/dev/null 2>&1; then
88
+ echo "::error::cosign is required but not available on the runner."
89
+ exit 1
90
+ fi
91
+ cosign version
92
+ cosign verify-blob \
93
+ --cert ".opengrep/opengrep.cert" \
94
+ --signature ".opengrep/opengrep.sig" \
95
+ --certificate-identity-regexp "https://github.com/opengrep/opengrep.+" \
96
+ --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
97
+ ".opengrep/opengrep"
98
+ echo "Opengrep ${OPENGREP_VERSION} signature verified."
99
+
100
+ - name: Print Opengrep version
101
+ run: |
102
+ set -euo pipefail
103
+ .opengrep/opengrep --version
104
+
105
+ - name: Run Opengrep scan (SARIF)
106
+ # Curated security-focused rule packs from the Semgrep registry:
107
+ # * p/security-audit / p/owasp-top-ten / p/cwe-top-25 — general
108
+ # * p/javascript / p/typescript — language-specific
109
+ # * p/secrets — hardcoded credentials / API keys
110
+ # `p/nodejs` is omitted on Deno because its sinks (e.g.
111
+ # `require()`, `process.*`) are not idiomatic on the Deno runtime.
112
+ run: |
113
+ set -euo pipefail
114
+ .opengrep/opengrep scan \
115
+ --config p/security-audit \
116
+ --config p/owasp-top-ten \
117
+ --config p/cwe-top-25 \
118
+ --config p/javascript \
119
+ --config p/typescript \
120
+ --config p/secrets \
121
+ --sarif-output opengrep.sarif \
122
+ --metrics off \
123
+ --timeout 120 \
124
+ --max-target-bytes 1500000 \
125
+ --exclude node_modules \
126
+ --exclude dist \
127
+ --exclude build \
128
+ --exclude coverage \
129
+ --exclude .opengrep \
130
+ .
131
+
132
+ - name: Upload SARIF to GitHub code scanning
133
+ if: always()
134
+ uses: github/codeql-action/upload-sarif@52485aec7be33610227643b0fe83936b8b5f061a # v3
135
+ with:
136
+ sarif_file: opengrep.sarif
137
+ category: opengrep
@@ -0,0 +1,121 @@
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 Deno service:
5
+ # Deno does not ship a built-in `audit` command, so without this
6
+ # workflow a scaffolded Deno project has NO scheduled SCA coverage
7
+ # at all. The Aikido write-up "SAST vs SCA" calls this gap out
8
+ # directly: SAST (CodeQL, Opengrep) only inspects your own source;
9
+ # it does not know that one of your `npm:` or `jsr:` imports just
10
+ # had a malware advisory published against the exact version your
11
+ # `deno.lock` pins.
12
+ #
13
+ # This workflow runs Google's OSV-Scanner against `deno.lock` on
14
+ # every PR and on a daily schedule. OSV-Scanner queries OSV.dev and
15
+ # cross-references the OpenSSF `malicious-packages` corpus
16
+ # (https://github.com/ossf/malicious-packages), so it surfaces both
17
+ # classic CVEs and explicitly-flagged malware drops — the exact
18
+ # classes of issue the September 2025 npm phishing wave that
19
+ # compromised `debug`, `chalk`, and ~2B downloads/week of dependent
20
+ # packages (https://lwn.net/Articles/1037167/) made famous.
21
+ #
22
+ # Defense-in-depth context (see SECURITY.md):
23
+ # * Deno's permission model (`--allow-net`, etc.) limits blast radius
24
+ # of a compromised dep at runtime.
25
+ # * `deno.lock` is checked into the repo and `deno task ...` runs
26
+ # with `--cached-only` in CI so an attacker cannot swap a dep
27
+ # between scan and run.
28
+ # * This OSV pass is the LAST layer: "did a pinned version turn out
29
+ # to carry a CVE or a malware advisory after we pinned it?"
30
+ #
31
+ # Hardening:
32
+ # * `permissions: {}` at the top level; the single job opts in to
33
+ # `contents: read` only. No write permissions of any kind.
34
+ # * `step-security/harden-runner` in audit mode.
35
+ # * `actions/checkout` runs with `persist-credentials: false`.
36
+ # * The OSV-Scanner binary is downloaded directly from the official
37
+ # google/osv-scanner GitHub release and its SHA-256 is verified
38
+ # against a locally-pinned constant BEFORE the binary is executed.
39
+ # We deliberately avoid the third-party `google/osv-scanner-action`
40
+ # wrapper so the only third-party surface is the binary itself, not
41
+ # a layer of action-side YAML that could be retagged.
42
+ # * The scan is read-only and runs on the committed lockfile without
43
+ # fetching or executing any dependency code.
44
+
45
+ name: OSV scan
46
+
47
+ on:
48
+ push:
49
+ branches: [main]
50
+ paths:
51
+ - "deno.lock"
52
+ - "deno.json"
53
+ - ".github/workflows/osv-scan.yml"
54
+ pull_request:
55
+ branches: [main]
56
+ paths:
57
+ - "deno.lock"
58
+ - "deno.json"
59
+ - ".github/workflows/osv-scan.yml"
60
+ schedule:
61
+ # 06:47 UTC daily — offset from Zizmor (07:00) so the SCA jobs do
62
+ # not collide.
63
+ - cron: "47 6 * * *"
64
+ workflow_dispatch:
65
+
66
+ permissions: {}
67
+
68
+ concurrency:
69
+ group: osv-scan-${{ github.workflow }}-${{ github.ref }}
70
+ cancel-in-progress: true
71
+
72
+ jobs:
73
+ scan:
74
+ name: OSV.dev + malicious-packages scan
75
+ runs-on: ubuntu-latest
76
+ timeout-minutes: 15
77
+ permissions:
78
+ contents: read
79
+
80
+ env:
81
+ # Pinned OSV-Scanner release. Bump together with the SHA-256 below.
82
+ # Source: https://github.com/google/osv-scanner/releases/tag/v2.3.8
83
+ OSV_SCANNER_VERSION: "2.3.8"
84
+ # SHA-256 of `osv-scanner_linux_amd64` from the v2.3.8
85
+ # `osv-scanner_SHA256SUMS` asset.
86
+ OSV_SCANNER_SHA256: "bc98e15319ed0d515e3f9235287ba53cdc5535d576d24fd573978ecfe9ab92dc"
87
+
88
+ steps:
89
+ - name: Harden runner
90
+ uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2
91
+ with:
92
+ egress-policy: audit
93
+ disable-sudo: true
94
+ disable-file-monitoring: false
95
+
96
+ - name: Checkout
97
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
98
+ with:
99
+ persist-credentials: false
100
+ show-progress: false
101
+
102
+ - name: Download and verify OSV-Scanner binary
103
+ # Pin the binary by SHA-256 BEFORE executing it. A registry or
104
+ # GitHub Releases compromise that swaps the asset would fail
105
+ # this check and abort the job.
106
+ run: |
107
+ set -euo pipefail
108
+ url="https://github.com/google/osv-scanner/releases/download/v${OSV_SCANNER_VERSION}/osv-scanner_linux_amd64"
109
+ curl --proto '=https' --tlsv1.2 --fail --silent --show-error --location \
110
+ --output osv-scanner "${url}"
111
+ echo "${OSV_SCANNER_SHA256} osv-scanner" | sha256sum --check --status
112
+ chmod +x osv-scanner
113
+ ./osv-scanner --version
114
+
115
+ - name: Scan deno.lock (OSV.dev + malicious-packages)
116
+ # Exits non-zero if any vulnerability or malware advisory matches
117
+ # a pinned `npm:` or `jsr:` version. This is the only scheduled
118
+ # SCA gate for a Deno scaffold, which has no built-in `audit`.
119
+ run: |
120
+ set -euo pipefail
121
+ ./osv-scanner scan source --lockfile=deno.lock --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 .
@@ -23,8 +23,10 @@ The `--with-ci` bundle adds these defaults:
23
23
  - Package-manager caches are disabled in CI to avoid cache-poisoning bridges.
24
24
  - Lockfile source verification rejects git dependencies and non-registry tarball URLs.
25
25
  - CodeQL, OpenSSF Scorecard, zizmor, Dependabot, and CODEOWNERS are generated.
26
+ - A weekly scheduled `opengrep.yml` runs a second SAST engine (Aikido's LGPL-2.1 fork of Semgrep) against the repository using the curated `p/security-audit`, `p/owasp-top-ten`, `p/cwe-top-25`, `p/javascript`, `p/typescript`, `p/nodejs`, and `p/secrets` rule packs. The Opengrep binary is downloaded from a pinned GitHub release and its sigstore cosign signature is verified against the official `opengrep/opengrep` release identity before it runs — no third-party action sits in the supply chain just for this scan. Running two SAST engines (CodeQL + Opengrep) catches different bug classes than either alone; see Aikido's [Ultimate SAST Guide](https://www.aikido.dev/blog/ultimate-sast-guide-static-application-security-testing) for why a layered SAST posture is the recommended default.
26
27
  - A daily scheduled `vuln-scan.yml` runs the package manager's audit against the committed lockfile so newly-disclosed CVEs are surfaced even when no PR or push has run CI (SOC 2 CC7.1 continuous-vulnerability-management evidence).
27
- - A weekly scheduled `dast.yml` boots the built application and runs an OWASP ZAP baseline scan against it. CodeQL + the `verify:*` family are the SAST half of the posture; this is the DAST half — see Aikido's [SAST vs DAST](https://www.aikido.dev/blog/sast-vs-dast-what-you-need-to-now) write-up for why both matter. Findings are summarized in the workflow log and the job fails on HIGH-risk alerts; MEDIUM / LOW / INFO are surfaced for triage but non-blocking.
28
+ - A weekly scheduled `dast.yml` boots the built application and runs an OWASP ZAP baseline scan against it. CodeQL + Opengrep + the `verify:*` family are the SAST half of the posture; this is the DAST half — see Aikido's [SAST vs DAST](https://www.aikido.dev/blog/sast-vs-dast-what-you-need-to-now) write-up for why both matter. Findings are summarized in the workflow log and the job fails on HIGH-risk alerts; MEDIUM / LOW / INFO are surfaced for triage but non-blocking.
29
+ - 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.
28
30
  - No npm publish workflow is generated: this scaffold is a REST API service, not a published package. If you later carve out a reusable library you can opt into npm trusted publishing yourself.
29
31
 
30
32
  ## Container hardening
@@ -57,11 +59,107 @@ SHA-pinned open-source tooling:
57
59
  - All findings are uploaded as SARIF to the GitHub **Code Scanning**
58
60
  tab alongside CodeQL.
59
61
 
62
+ ## Runtime hardening (`docker run`, Compose, Fly machines)
63
+
64
+ The image build is only half the story. The Aikido
65
+ ["Container Security — The Dev Guide"](https://www.aikido.dev/blog/container-security-guide)
66
+ write-up calls out that most container compromises chain a known
67
+ software CVE with a permissive runtime configuration: a container
68
+ launched with the default capability set, no resource limits, and a
69
+ writable root filesystem turns a single RCE into a host compromise.
70
+ Apply these flags wherever you launch the image outside of Kubernetes
71
+ (the **Pod security on Kubernetes** entry below covers the equivalent
72
+ `PodSpec` settings):
73
+
74
+ - `--read-only` — root filesystem is read-only. The shipped image
75
+ needs no writable paths at runtime; pair with
76
+ `--tmpfs /tmp:rw,noexec,nosuid,size=64m` if your app writes
77
+ temp files.
78
+ - `--cap-drop=ALL` — drop every Linux capability. The Node.js
79
+ runtime under `tini` needs none of the default set
80
+ (`CHOWN`, `DAC_OVERRIDE`, `NET_BIND_SERVICE`, …); rebind the
81
+ listening port to `>= 1024` (the shipped `EXPOSE 3000`
82
+ already does) so you do not need `NET_BIND_SERVICE` back.
83
+ - `--security-opt=no-new-privileges:true` — block `setuid` /
84
+ `setgid` / file-capability escalation inside the container, so a
85
+ compromised handler cannot regain privileges the image dropped.
86
+ - `--security-opt=seccomp=default` — keep Docker's default seccomp
87
+ profile (it is on by default, but some orchestrators disable it;
88
+ re-assert it explicitly).
89
+ - `--pids-limit=256` — cap process count. Prevents fork-bomb style
90
+ DoS from a compromised handler.
91
+ - `--memory=512m --memory-swap=512m --cpus=1.0` — enforce resource
92
+ limits. The framework's `loadShedding()` middleware refuses
93
+ requests under event-loop / RSS / heap pressure, but the
94
+ kernel-side limits are the backstop that makes a runaway
95
+ handler a `137 OOMKilled` instead of a noisy neighbour.
96
+ - **Never** pass `--privileged`, `-v /var/run/docker.sock:…`, or
97
+ `--pid=host` / `--network=host` to this image. None of them are
98
+ needed by a stateless HTTP service and each one converts a
99
+ container escape into a host takeover.
100
+
101
+ Equivalent `compose.yml` snippet (drop into the service block your
102
+ deploy uses):
103
+
104
+ ```yaml
105
+ services:
106
+ app:
107
+ image: ghcr.io/your-org/your-app:sha-<digest>
108
+ read_only: true
109
+ tmpfs:
110
+ - /tmp:rw,noexec,nosuid,size=64m
111
+ cap_drop: ["ALL"]
112
+ security_opt:
113
+ - "no-new-privileges:true"
114
+ pids_limit: 256
115
+ mem_limit: 512m
116
+ memswap_limit: 512m
117
+ cpus: 1.0
118
+ restart: unless-stopped
119
+ ```
120
+
121
+ Fly.io, Render, Railway, and similar PaaS surfaces apply most of these
122
+ defaults for you, but check the platform documentation — `--privileged`
123
+ and host network are still escape hatches you should keep off.
124
+
125
+ ## Cloud posture (operator checklist)
126
+
127
+ The framework cannot author your cloud configuration for you, but the
128
+ [Aikido "Top 7 Cloud Security Vulnerabilities"](https://www.aikido.dev/blog/top-cloud-security-vulnerabilities)
129
+ write-up maps cleanly onto a short checklist. Adopt these alongside the
130
+ container and CI defaults above:
131
+
132
+ - **IMDSv2 only.** On EC2 / equivalent, require token-based IMDSv2 and disable
133
+ IMDSv1 at the launch-template / instance-metadata-options level. Combined
134
+ with the framework's `fetchGuard()` on user-controlled outbound fetches,
135
+ this closes the Capital One 2019 / Pandoc CVE-2025-51591 SSRF → IAM chain.
136
+ - **Least-privilege execution role.** Scope the workload's IAM role to the
137
+ minimum actions and resource ARNs the handler actually calls (e.g.
138
+ `s3:GetObject` on a single bucket prefix, never `s3:*`). Enable IAM Access
139
+ Analyzer / unused-permission scans and prune quarterly.
140
+ - **Pod security on Kubernetes.** Deploy with `runAsNonRoot: true`,
141
+ `readOnlyRootFilesystem: true`, `allowPrivilegeEscalation: false`,
142
+ `capabilities: { drop: ["ALL"] }`, and `automountServiceAccountToken: false`
143
+ on pods that do not call the kube API. The shipped `Dockerfile` already
144
+ runs as non-root UID 1001 with no writable paths required at runtime, so
145
+ these settings work out of the box.
146
+ - **Network segmentation.** Keep development, staging, and production in
147
+ separate cloud accounts / projects. Apply default-deny `NetworkPolicy` (k8s)
148
+ or Security Group egress rules so a compromised workload cannot freely
149
+ reach the metadata service, the database tier, or the internet.
150
+ - **Log agent isolation.** If you run FluentBit / Vector / Fluentd as a
151
+ sidecar or DaemonSet, run it under its own ServiceAccount with read-only
152
+ access to the log path, pin the agent image to a digest, and subscribe to
153
+ its CVE feed (FluentBit had CVE-2025-12969 auth bypass + CVE-2025-12972
154
+ path traversal disclosed in 2025). The framework's structured logger
155
+ already redacts known credential shapes **before** the line leaves the
156
+ process, so a compromised agent receives pre-redacted data.
157
+
60
158
  ## Required repository settings
61
159
 
62
160
  Before relying on these files for a company project:
63
161
 
64
162
  1. Replace `@your-org/security-team` in `.github/CODEOWNERS` or pass `--code-owner` when scaffolding.
65
- 2. Protect the `main` branch and require the CI, CodeQL, Scorecard, and zizmor checks.
163
+ 2. Protect the `main` branch and require the CI, CodeQL, Opengrep, Scorecard, and zizmor checks.
66
164
  3. Enable GitHub secret scanning and push protection.
67
165
  4. Keep `ignore-scripts=true` and the `pnpm-workspace.yaml` supply-chain settings on when using pnpm.
@@ -7,7 +7,9 @@
7
7
  /.github/ __CODE_OWNER__
8
8
  /.github/workflows/ __CODE_OWNER__
9
9
  /.github/workflows/vuln-scan.yml __CODE_OWNER__
10
+ /.github/workflows/osv-scan.yml __CODE_OWNER__
10
11
  /.github/workflows/dast.yml __CODE_OWNER__
12
+ /.github/workflows/secret-scan.yml __CODE_OWNER__
11
13
  /.github/dependabot.yml __CODE_OWNER__
12
14
  /.github/CODEOWNERS __CODE_OWNER__
13
15
 
@@ -1,18 +1,28 @@
1
- # Container security scan generated by create-daloy --with-ci.
1
+ # Container + IaC security scan generated by create-daloy --with-ci.
2
2
  #
3
3
  # Inspired by the controls in
4
- # https://snyk.io/blog/scale-container-security-effortlessly/ — but built
5
- # only from SHA-pinned, free, open-source GitHub Actions:
4
+ # https://snyk.io/blog/scale-container-security-effortlessly/ and
5
+ # Aikido's "IaC security scanning for Terraform & Kubernetes
6
+ # misconfigurations"
7
+ # (https://www.aikido.dev/blog/iac-security-scanning-terraform-kubernetes-misconfigurations)
8
+ # — but built only from SHA-pinned, free, open-source GitHub Actions:
6
9
  #
7
10
  # - hadolint lints the Dockerfile for known anti-patterns and unsafe
8
11
  # instructions (CIS Docker Benchmark coverage).
9
- # - Trivy scans the source tree (config + secrets + vulnerable lockfile
10
- # entries), then scans the built image for OS + language CVEs.
12
+ # - Trivy filesystem scan with the `misconfig` scanner enabled catches
13
+ # IaC misconfigurations in Terraform (`*.tf` / `*.tfvars`),
14
+ # Kubernetes manifests (`k8s/**`, `deployment.yaml`,
15
+ # `kustomization*.yaml`), Helm charts, Dockerfiles, and
16
+ # CloudFormation templates — plus secrets and vulnerable lockfile
17
+ # entries. This runs even when no Dockerfile is present, so a
18
+ # project that only ships Terraform / K8s still gets the Aikido-style
19
+ # IaC posture review on every PR.
20
+ # - Trivy image scan covers OS + language CVEs once the image is built.
11
21
  # - SARIF results are uploaded to the Code Scanning tab so findings show
12
22
  # up in the same place as CodeQL.
13
23
  #
14
- # The job is a no-op if the project has no Dockerfile at the repo root, so
15
- # it is safe to keep enabled for templates that disable the container.
24
+ # The Dockerfile-specific steps (hadolint, build, image scan) are a no-op
25
+ # if the project has no Dockerfile at the repo root.
16
26
 
17
27
  name: Container scan
18
28
 
@@ -26,6 +36,17 @@ on:
26
36
  - "package-lock.json"
27
37
  - "yarn.lock"
28
38
  - "bun.lock"
39
+ - "**/*.tf"
40
+ - "**/*.tfvars"
41
+ - "**/*.hcl"
42
+ - "k8s/**"
43
+ - "helm/**"
44
+ - "**/deployment.yaml"
45
+ - "**/deployment.yml"
46
+ - "**/kustomization.yaml"
47
+ - "**/kustomization.yml"
48
+ - "**/Chart.yaml"
49
+ - "**/values.yaml"
29
50
  - ".github/workflows/container-scan.yml"
30
51
  push:
31
52
  branches: [main]
@@ -37,6 +58,17 @@ on:
37
58
  - "package-lock.json"
38
59
  - "yarn.lock"
39
60
  - "bun.lock"
61
+ - "**/*.tf"
62
+ - "**/*.tfvars"
63
+ - "**/*.hcl"
64
+ - "k8s/**"
65
+ - "helm/**"
66
+ - "**/deployment.yaml"
67
+ - "**/deployment.yml"
68
+ - "**/kustomization.yaml"
69
+ - "**/kustomization.yml"
70
+ - "**/Chart.yaml"
71
+ - "**/values.yaml"
40
72
  - ".github/workflows/container-scan.yml"
41
73
  schedule:
42
74
  # Weekly run catches newly-disclosed base-image CVEs even when the
@@ -130,12 +162,17 @@ jobs:
130
162
  sarif_file: hadolint.sarif
131
163
  category: hadolint
132
164
 
133
- - name: Trivy filesystem scan (config + secrets + vulns)
134
- if: steps.detect.outputs.present == 'true'
165
+ - name: Trivy filesystem scan (IaC misconfig + secrets + vulns)
166
+ # `misconfig` is the IaC posture scanner — Terraform, Kubernetes,
167
+ # Helm, Dockerfile, CloudFormation — per Aikido's "IaC security
168
+ # scanning for Terraform & Kubernetes misconfigurations" article.
169
+ # It runs unconditionally so a project that only ships Terraform
170
+ # or K8s (no Dockerfile) still gets reviewed on every PR.
135
171
  uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
136
172
  with:
137
173
  scan-type: fs
138
174
  scan-ref: .
175
+ scanners: vuln,secret,misconfig
139
176
  severity: HIGH,CRITICAL
140
177
  ignore-unfixed: true
141
178
  format: sarif
@@ -143,7 +180,6 @@ jobs:
143
180
  exit-code: "0"
144
181
 
145
182
  - name: Upload Trivy filesystem SARIF
146
- if: steps.detect.outputs.present == 'true'
147
183
  uses: github/codeql-action/upload-sarif@52485aec7be33610227643b0fe83936b8b5f061a # v3
148
184
  with:
149
185
  sarif_file: trivy-fs.sarif