Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .github/workflows/dco.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: DCO
on:
pull_request:
branches: [main]
permissions:
contents: read
pull-requests: read
jobs:
dco:
runs-on: ubuntu-24.04
steps:
- name: Verify Signed-off-by on every commit
env:
GH_TOKEN: ${{ github.token }}
COMMITS_URL: ${{ github.event.pull_request.commits_url }}
PR_COMMITS: ${{ github.event.pull_request.commits }}
run: |
set -eu
case "$PR_COMMITS" in
''|*[!0-9]*) echo "::error::the PR commit count is missing or not a number; cannot verify every commit"; exit 1;;
esac
missing=0
checked=0
# Page through the PR commits and check each message for the trailer.
page=1
while :; do
batch="$(gh api "${COMMITS_URL}?per_page=100&page=${page}" \
--jq '.[] | @base64')"
[ -z "$batch" ] && break
for row in $batch; do
msg="$(printf '%s' "$row" | base64 -d | \
python3 -c 'import sys,json;d=json.load(sys.stdin);print(d["commit"]["message"]);print(d["sha"])')"
sha="$(printf '%s\n' "$msg" | tail -n1)"
checked=$((checked+1))
body="$(printf '%s\n' "$msg" | sed '$d')"
if ! printf '%s\n' "$body" | grep -qiE '^Signed-off-by: .+ <.+@.+>'; then
echo "::error::commit ${sha} missing Signed-off-by trailer (run: git commit -s)"
missing=1
fi
done
page=$((page+1))
done
# The commits API lists at most 250 commits. Fail closed when it returned fewer
# than the PR holds, so an unchecked commit cannot pass.
if [ "$checked" -ne "$PR_COMMITS" ]; then
echo "::error::checked ${checked} of ${PR_COMMITS} PR commits; the commits API cannot list them all. Split the PR."
missing=1
fi
[ "$missing" -eq 0 ] || exit 1
173 changes: 173 additions & 0 deletions .github/workflows/guard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
name: guard
on:
pull_request:
branches: [main]
# edited: a description edit re-runs the issue-link check (#838).
types: [opened, synchronize, reopened, edited]
push:
branches: [main]
permissions:
contents: read
concurrency:
group: guard-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
gitleaks:
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 — full history for the PR commit range
with:
fetch-depth: 0
persist-credentials: false
- name: Resolve config from BASE (never trust the PR-controlled .gitleaks.toml)
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: |
set -eu
if [ -n "${BASE_SHA:-}" ]; then
# pull_request: scan with the base branch's config, not the PR's
git show "${BASE_SHA}:.gitleaks.toml" > /tmp/gitleaks.toml
else
# push to main: the checked-out file IS the trusted config
cp .gitleaks.toml /tmp/gitleaks.toml
fi
- name: gitleaks (pinned binary + sha256)
env:
GL_VERSION: "8.21.2"
GL_SHA256: "5bc41815076e6ed6ef8fbecc9d9b75bcae31f39029ceb55da08086315316e3ba"
run: |
set -eu
curl -fsSLo /tmp/gl.tgz "https://github.com/gitleaks/gitleaks/releases/download/v${GL_VERSION}/gitleaks_${GL_VERSION}_linux_x64.tar.gz"
echo "${GL_SHA256} /tmp/gl.tgz" | sha256sum -c -
tar -xzf /tmp/gl.tgz -C /tmp gitleaks
/tmp/gitleaks detect --source . --no-git --config /tmp/gitleaks.toml --redact --no-banner --exit-code 1
- name: Sanity-lint the PR's proposed .gitleaks.toml (non-blocking on content)
if: github.event_name == 'pull_request'
run: /tmp/gitleaks detect --no-git --source /dev/null --config .gitleaks.toml >/dev/null 2>&1 || echo "::warning::PR .gitleaks.toml changed — review the allowlist diff (CODEOWNERS-gated)"

commitlint:
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
contents: read
pull-requests: read
if: github.event_name == 'pull_request'
steps:
- name: Conventional Commits check (inline, PR commits API)
env:
GH_TOKEN: ${{ github.token }}
COMMITS_URL: ${{ github.event.pull_request.commits_url }}
PR_COMMITS: ${{ github.event.pull_request.commits }}
run: |
set -eu
case "$PR_COMMITS" in
''|*[!0-9]*) echo "::error::the PR commit count is missing or not a number; cannot verify every commit"; exit 1;;
esac
bad=0
checked=0
re='^(feat|fix|docs|chore|refactor|test|build|ci|perf|revert|style)(\([a-z0-9._-]+\))?!?: .+'
page=1
while :; do
batch="$(gh api "${COMMITS_URL}?per_page=100&page=${page}" --jq '.[] | @base64')"
[ -z "$batch" ] && break
for row in $batch; do
msg="$(printf '%s' "$row" | base64 -d | python3 -c 'import sys,json;print(json.load(sys.stdin)["commit"]["message"])')"
sha="$(printf '%s' "$row" | base64 -d | python3 -c 'import sys,json;print(json.load(sys.stdin)["sha"][:7])')"
subj="$(printf '%s\n' "$msg" | head -n 1)"
checked=$((checked+1))
# Any commit, merge commits included, may carry a session link (#838).
# Name the commit by SHA only: the log is public, so it must not echo
# a message that holds the link. The match is case-insensitive because
# hostnames are; it targets accidental links, not deliberate obfuscation.
if printf '%s\n' "$msg" | grep -qiE 'claude\.ai/code/session'; then
echo "::error::commit ${sha} links to an agent session; remove the link from its message"
bad=1
continue
fi
case "$subj" in "Merge "*) continue;; esac
if ! printf '%s' "$subj" | grep -qE "$re"; then
echo "::error::commit ${sha} has a non-conventional subject; use type(scope): summary"
bad=1
fi
done
page=$((page+1))
done
# The commits API lists at most 250 commits. Fail closed when it returned fewer
# than the PR holds, so an unchecked commit cannot pass.
if [ "$checked" -ne "$PR_COMMITS" ]; then
echo "::error::checked ${checked} of ${PR_COMMITS} PR commits; the commits API cannot list them all. Split the PR."
bad=1
fi
[ "$bad" -eq 0 ] || exit 1

trivy:
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: trivy fs — vuln only (pinned binary + sha256)
env:
TRIVY_VERSION: "0.71.0"
TRIVY_SHA256: "30a3d22b23f88c233f1658f562fb477cae3b3e8b4761109d515b7698daf85814"
run: |
set -eu
curl -fsSLo /tmp/trivy.tgz "https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz"
echo "${TRIVY_SHA256} /tmp/trivy.tgz" | sha256sum -c -
tar -xzf /tmp/trivy.tgz -C /tmp trivy
/tmp/trivy fs --scanners vuln --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1 --no-progress .

actionlint:
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: actionlint (pinned binary + sha256)
env:
AL_VERSION: "1.7.7"
AL_SHA256: "023070a287cd8cccd71515fedc843f1985bf96c436b7effaecce67290e7e0757"
run: |
set -eu
curl -fsSLo /tmp/al.tgz "https://github.com/rhysd/actionlint/releases/download/v${AL_VERSION}/actionlint_${AL_VERSION}_linux_amd64.tar.gz"
echo "${AL_SHA256} /tmp/al.tgz" | sha256sum -c -
tar -xzf /tmp/al.tgz -C /tmp actionlint
/tmp/actionlint -color

issue-link:
# Working-model gate (umbrella issue dmfdeploy/dmfdeploy#32): every PR must
# reference its tracking issue in the canonical backlog FULLY QUALIFIED —
# bare #N resolves to this repo's own issues and silently misses. Trivial
# changes: add the no-issue label.
if: github.event_name == 'pull_request'
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: PR references a canonical-backlog issue (qualified)
env:
PR_BODY: ${{ github.event.pull_request.body }}
PR_LABELS: ${{ join(github.event.pull_request.labels.*.name, ' ') }}
run: |
set -eu
# A link to a private agent session does not belong in a public PR (#838).
# This runs before the no-issue exit, so the label cannot skip it. The match
# is case-insensitive because hostnames are; it targets accidental links,
# not deliberate obfuscation.
if printf '%s' "${PR_BODY}" | grep -qiE 'claude\.ai/code/session'; then
echo "FAIL: PR description links to an agent session; remove the link." >&2
exit 1
fi
case " ${PR_LABELS} " in *" no-issue "*)
echo "OK: no-issue label set"; exit 0 ;;
esac
if printf '%s' "${PR_BODY}" | grep -qiE '(close[sd]?|fix(e[sd])?|resolve[sd]?|refs?)[[:space:]:]+(dmfdeploy/dmfdeploy#[0-9]+|https://github\.com/dmfdeploy/dmfdeploy/issues/[0-9]+)'; then
echo "OK: qualified issue reference found"
exit 0
fi
echo "FAIL: PR body must reference its tracking issue FULLY QUALIFIED, e.g. 'Refs dmfdeploy/dmfdeploy#32'." >&2
echo "Bare #N points at THIS repo, not the canonical backlog — see umbrella docs/WORKING-MODEL.md." >&2
echo "Trivial change? Add the 'no-issue' label." >&2
exit 1
67 changes: 67 additions & 0 deletions .gitleaks.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# dmfdeploy platform — sub-repo gitleaks config
#
# Mirrors the public-safe subset of the umbrella's .gitleaks.toml so each
# public repo's local pre-commit catches the same leak categories:
# - generic-api-key (default), allowlisted under docs/
# - dmf-dev-changeme: known placeholder credential
# - dmf-private-network-literal: concrete private or mesh IPs
# - dmf-macos-metadata: macOS metadata leaks
#
# Public repos intentionally do not encode operator-specific identity
# strings in this file. Those remain in the private umbrella scrub gate.
#
# Add new exceptions only with a comment explaining why and a path
# anchored as narrowly as possible.

[extend]
useDefault = true

# Global allowlist for gitignored local working-tree noise.
[allowlist]
description = "local working-tree noise (gitignored)"
paths = [
'''^\.venv/''',
'''^node_modules/''',
'''^\.terraform/''',
'''^\.claude/settings\.local\.json$''',
]

# Per-rule allowlist for the default generic-api-key rule.
[[rules]]
id = "generic-api-key"
[[rules.allowlists]]
description = "docs/ prose references to secret/token concepts"
paths = [
'''^docs/'''
]

# Catch a known placeholder credential if it appears outside documentation.
[[rules]]
id = "dmf-dev-changeme"
description = "Known placeholder credential"
regex = '''dev[:]changeme'''
tags = ["placeholder-cred", "dmf"]

# Catch concrete private-network details in public-facing repos. Example
# addresses belong in placeholder form (`<node-private-ip>`) or under the
# private environment repo, not here.
[[rules]]
id = "dmf-private-network-literal"
description = "Concrete private or mesh IP address"
regex = '''\b(10\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}|172\.(1[6-9]|2[0-9]|3[01])\.[0-9]{1,3}\.[0-9]{1,3}|192\.168\.[0-9]{1,3}\.[0-9]{1,3}|100\.(6[4-9]|[78][0-9]|9[0-9]|10[0-9]|11[0-9]|12[0-7])\.[0-9]{1,3}\.[0-9]{1,3})\b'''
tags = ["topology", "dmf"]

# Catch macOS metadata leaking into the tracked tree.
[[rules]]
id = "dmf-macos-metadata"
description = "macOS metadata file in tracked tree"
regex = '''\.DS[_]Store'''
tags = ["operator-identity", "dmf"]
[[rules.allowlists]]
description = "files that intentionally name the artifact"
paths = [
'''^\.gitleaks\.toml$''',
'''(^|/)\.gitignore$''',
'''(^|/)\.helmignore$''',
'''(^|/)docs/DEVELOPMENT-AND-BUILD-RULES\.md$''',
]
Loading