create-daloy 0.34.3 → 0.35.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -20
- package/bin/create-daloy.mjs +83 -17
- package/package.json +2 -2
- package/sbom.cdx.json +13 -9
- package/sbom.spdx.json +5 -5
- package/templates/_ci/deno/SECURITY.md +31 -2
- package/templates/_ci/deno/_github/CODEOWNERS +2 -0
- package/templates/_ci/deno/_github/workflows/container-scan.yml +42 -8
- package/templates/_ci/deno/_github/workflows/deploy.yml +68 -3
- package/templates/_ci/deno/_github/workflows/opengrep.yml +137 -0
- package/templates/_ci/deno/_github/workflows/osv-scan.yml +121 -0
- package/templates/_ci/deno/_github/workflows/secret-scan.yml +106 -0
- package/templates/_ci/node/SECURITY.md +100 -2
- package/templates/_ci/node/_github/CODEOWNERS +2 -0
- package/templates/_ci/node/_github/workflows/container-scan.yml +46 -10
- package/templates/_ci/node/_github/workflows/opengrep.yml +169 -0
- package/templates/_ci/node/_github/workflows/osv-scan.yml +135 -0
- package/templates/_ci/node/_github/workflows/secret-scan.yml +106 -0
- package/templates/bun-basic/AGENTS.md +12 -0
- package/templates/bun-basic/_gitignore +5 -0
- package/templates/bun-basic/package.json +3 -2
- package/templates/cloudflare-worker/AGENTS.md +13 -0
- package/templates/cloudflare-worker/_gitignore +9 -0
- package/templates/cloudflare-worker/_npmrc +2 -1
- package/templates/cloudflare-worker/package.json +3 -2
- package/templates/deno-basic/AGENTS.md +12 -0
- package/templates/deno-basic/_gitignore +5 -0
- package/templates/deno-basic/deno.json +2 -2
- package/templates/node-basic/AGENTS.md +12 -0
- package/templates/node-basic/_gitignore +5 -0
- package/templates/node-basic/_npmrc +2 -2
- package/templates/node-basic/package.json +1 -1
- package/templates/vercel-edge/AGENTS.md +12 -0
- package/templates/vercel-edge/_gitignore +5 -0
- package/templates/vercel-edge/_npmrc +2 -1
- package/templates/vercel-edge/package.json +3 -2
|
@@ -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/
|
|
5
|
-
#
|
|
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
|
|
10
|
-
#
|
|
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
|
|
15
|
-
#
|
|
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 (
|
|
134
|
-
|
|
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
|