Skip to content

Commit 9f22633

Browse files
Initial commit
0 parents  commit 9f22633

6 files changed

Lines changed: 272 additions & 0 deletions

File tree

‎.github/CODEOWNERS‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* @checkmarx/cx-sec-pub
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: Scan for GitHub Actions issues
2+
3+
on:
4+
pull_request:
5+
6+
concurrency:
7+
group: ${{ github.workflow }}-${{ github.repository }}-${{ github.ref }}
8+
9+
permissions: {}
10+
11+
jobs:
12+
main:
13+
name: Scan repository contents
14+
runs-on: cx-public-ubuntu-x64
15+
permissions:
16+
contents: read
17+
steps:
18+
- name: Check out repository
19+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
20+
with:
21+
persist-credentials: false
22+
23+
- name: Run Zizmor linter
24+
uses: step-security/zizmor-action@e4ba943ab717e2dc71e7276b8649f48fb7f2519d # v0.5.7
25+
with:
26+
advanced-security: false
27+
annotations: false
28+
persona: pedantic
29+
fail-on-no-inputs: false
30+
# TODO: Zizmor needs a token so it can read actions from other internal repos.
31+
online-audits: false
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
name: Security
2+
3+
on:
4+
pull_request:
5+
branches: [main, master]
6+
7+
# One run per PR; a new push cancels the in-progress scan (no duplicate
8+
# comments, no wasted runner minutes).
9+
concurrency:
10+
group: secret-scan-${{ github.event.pull_request.number }}
11+
cancel-in-progress: true
12+
13+
permissions: {} # no broad grants at workflow level
14+
15+
jobs:
16+
secret-scan:
17+
name: TruffleHog Secret Scan
18+
runs-on: cx-public-ubuntu-x64
19+
timeout-minutes: 5
20+
permissions:
21+
contents: read # required to check out the PR head
22+
pull-requests: write # required to upsert the findings comment
23+
24+
steps:
25+
- name: Checkout repository
26+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
27+
with:
28+
fetch-depth: 0
29+
ref: ${{ github.event.pull_request.head.sha }}
30+
persist-credentials: false
31+
32+
- name: Run TruffleHog
33+
id: trufflehog
34+
shell: bash
35+
env:
36+
# Pinned by immutable digest (tag 3.96.0). Bump digest + comment on upgrade.
37+
TRUFFLEHOG_IMAGE: "ghcr.io/trufflesecurity/trufflehog@sha256:aa821cf4ace8861c7d096d83818cdf7bb9719028a52d37a52eaad44086a52577" # 3.96.0
38+
BASE_SHA: ${{ github.event.pull_request.base.sha }}
39+
run: |
40+
set -euo pipefail
41+
# --no-fail: findings exit 0, so TruffleHog only exits nonzero on real
42+
# breakage (image pull, daemon, bad flags). With `set -e` and no `|| true`,
43+
# that breakage fails the step (and, with no continue-on-error, the job) —
44+
# instead of silently producing empty results. We block on VERIFIED secrets
45+
# ourselves in a later step.
46+
ARGS=(git "file:///repo" --json --no-update --no-fail)
47+
if [[ -n "${BASE_SHA}" && ! "${BASE_SHA}" =~ ^0+$ ]]; then
48+
ARGS+=(--since-commit "${BASE_SHA}")
49+
fi
50+
echo "Running: trufflehog ${ARGS[*]}"
51+
docker run --rm -v "${GITHUB_WORKSPACE}:/repo:ro" -w /repo \
52+
"${TRUFFLEHOG_IMAGE}" "${ARGS[@]}" > trufflehog-results.json
53+
echo "--- raw results ---"; cat trufflehog-results.json
54+
55+
- name: Parse results
56+
id: parse
57+
if: always()
58+
shell: bash
59+
run: |
60+
set -uo pipefail
61+
grep -E '"DetectorName"' trufflehog-results.json > findings.ndjson || true
62+
# Count non-empty lines. grep -c prints "0" AND exits 1 on no match, so
63+
# capture once and only reset on failure (avoids a duplicate "0").
64+
COUNT=$(grep -c . findings.ndjson 2>/dev/null) || COUNT=0
65+
# Verified = confirmed-live by the provider (compact JSON -> "Verified":true).
66+
# These are what we BLOCK on; everything else is a warning.
67+
VERIFIED=$(grep -c '"Verified":true' findings.ndjson 2>/dev/null) || VERIFIED=0
68+
echo "count=${COUNT}" >> "$GITHUB_OUTPUT"
69+
echo "verified=${VERIFIED}" >> "$GITHUB_OUTPUT"
70+
echo "Total=${COUNT} verified(blocking)=${VERIFIED} unverified(warn)=$((COUNT - VERIFIED))"
71+
72+
- name: Upsert single PR comment
73+
if: always()
74+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
75+
with:
76+
script: |
77+
const fs = require('fs');
78+
const MARKER = '<!-- trufflehog-secret-scan -->';
79+
const server = process.env.GITHUB_SERVER_URL;
80+
const { owner, repo } = context.repo;
81+
const issue_number = context.payload.pull_request.number;
82+
83+
const findings = fs.existsSync('findings.ndjson')
84+
? fs.readFileSync('findings.ndjson', 'utf8').split('\n').filter(Boolean).map(l => JSON.parse(l))
85+
: [];
86+
const n = findings.length;
87+
88+
const verifiedCount = findings.filter(f => f.Verified === true).length;
89+
const unverifiedCount = n - verifiedCount;
90+
91+
let body;
92+
if (n > 0) {
93+
const seen = new Set();
94+
const rows = [];
95+
for (const f of findings) {
96+
const g = f.SourceMetadata?.Data?.Git || {};
97+
const type = f.DetectorName || 'Unknown';
98+
const status = f.Verified ? '✅ verified' : '⚠️ unverified';
99+
const commit = g.commit || '';
100+
const file = g.file || '(unknown)';
101+
const line = g.line || 0;
102+
103+
let view = '—';
104+
if (commit && file !== '(unknown)') {
105+
const anchor = line > 0 ? `#L${line}` : '';
106+
view = `[🔎 View secret](${server}/${owner}/${repo}/blob/${commit}/${file}${anchor})`;
107+
}
108+
109+
const key = `${type}|${file}|${line}|${commit}`;
110+
if (seen.has(key)) continue;
111+
seen.add(key);
112+
rows.push(`| ${type} | ${status} | \`${commit.slice(0,10)}\` | \`${file}\` | ${view} |`);
113+
}
114+
const author = context.payload.pull_request.user.login;
115+
// Tiered: VERIFIED secrets block the merge; unverified are warnings.
116+
const header = verifiedCount > 0
117+
? '## 🚨 Verified secret(s) detected — merge blocked'
118+
: '## ⚠️ Potential secret(s) detected (unverified)';
119+
const footer = verifiedCount > 0
120+
? '> ⛔ This PR is **blocked**: a verified, live secret was found. Remove it and re-scan to merge.'
121+
: '> ⚠️ These are **unverified** matches and do **not** block the merge — please review and confirm they are not real secrets.';
122+
body = [
123+
MARKER,
124+
header,
125+
'',
126+
`@${author} — TruffleHog found **${n}** potential secret(s) in this PR ` +
127+
`(**${verifiedCount} verified**, ${unverifiedCount} unverified).`,
128+
'',
129+
'| Secret Type | Status | Commit | File Path | View |',
130+
'| --- | --- | --- | --- | --- |',
131+
...rows,
132+
'',
133+
'### 🛠 Guidelines to remediate hardcoded secrets',
134+
'- Understand the implications of revoking this secret by investigating where it is used in your code.',
135+
'- **Immediately rotate/revoke** the exposed secret. (https://howtorotate.com/docs/introduction/getting-started/)',
136+
'- Remove it from git history ( **a new "remove secret" commit is NOT enough** e.g. with [`git-filter-repo`](https://github.com/newren/git-filter-repo)).',
137+
'',
138+
'To avoid such incidents in the future consider:',
139+
'- Following the best practices for managing and storing secrets, including API keys and other credentials.',
140+
'- Re-scan after cleanup before merging.',
141+
'',
142+
footer,
143+
].join('\n');
144+
} else {
145+
body = [
146+
MARKER,
147+
'## ✅ No secrets detected',
148+
'',
149+
'TruffleHog found no secrets in the current commits of this PR.',
150+
].join('\n');
151+
}
152+
153+
const comments = await github.paginate(github.rest.issues.listComments, {
154+
owner, repo, issue_number, per_page: 100,
155+
});
156+
const existing = comments.find(c => c.body && c.body.includes(MARKER));
157+
158+
if (existing) {
159+
await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body });
160+
} else if (n > 0) {
161+
await github.rest.issues.createComment({ owner, repo, issue_number, body });
162+
}
163+
164+
# Block the merge ONLY on verified (confirmed-live) secrets. Unverified
165+
# findings are surfaced in the PR comment but do not fail the check.
166+
- name: Fail if verified secrets were found
167+
if: steps.parse.outputs.verified != '0'
168+
env:
169+
VERIFIED: ${{ steps.parse.outputs.verified }} # <-- add
170+
run: |
171+
echo "::error::TruffleHog found ${VERIFIED} VERIFIED secret(s). Merge is blocked."
172+
exit 1

‎.gitignore‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# IDE
2+
.vscode/
3+
.idea/
4+
5+
# OS
6+
.DS_Store
7+
8+
# Claude Code local settings
9+
.claude/settings.local.json

‎CLAUDE.md‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Repository purpose
6+
7+
This repo holds GitHub Actions **workflows** (and, eventually, reusable **actions**) maintained by the
8+
Checkmarx DevOps Scale and Security team, for use across other CheckmarxDev repos. There is no
9+
application source code here — the deliverables are `.yml` workflow definitions under `.github/workflows/`.
10+
11+
## Repository structure
12+
13+
- `.github/workflows/` — workflow definitions, each independent and self-contained
14+
- `.github/CODEOWNERS` — all files owned by `@checkmarxdev/cx-devops-scale-and-security`
15+
- `README.md` — brief description of each workflow/action; keep it in sync when adding or renaming one
16+
17+
## Current workflows
18+
19+
- **github-actions-linter.yml** — runs on `pull_request`; lints the repo's own workflow/action YAML using
20+
Zizmor (`step-security/zizmor-action`), `persona: pedantic`, online audits disabled (no token available yet
21+
to read actions from other internal repos — see the `TODO` in the file).
22+
- **trufflehog-secret-scan.yml** — runs on `pull_request` (main/master); scans PR commits for secrets using
23+
a pinned TruffleHog image (referenced by digest, tagged with version in a comment), posts/updates a single
24+
PR comment (marker-based upsert) summarizing findings, and fails the job only when a **verified** (live)
25+
secret is found. Unverified findings are warnings only and do not block merge.
26+
27+
## Conventions to follow when adding or editing workflows
28+
29+
- **Pin all third-party actions to a full commit SHA**, with the version as a trailing comment
30+
(e.g. `uses: actions/checkout@<sha> # v6.0.3`). Same pattern for Docker images: pin by digest with the
31+
human-readable tag/version in a trailing comment.
32+
- **Default `permissions: {}` at the workflow level**; grant the minimum needed (`contents: read`, etc.) at
33+
the job level only.
34+
- **Set `concurrency`** on PR-triggered workflows to avoid duplicate/overlapping runs — either a plain
35+
workflow/repo/ref group, or `cancel-in-progress: true` with a PR-number-scoped group when duplicate PR
36+
comments must be avoided.
37+
- Non-obvious `bash` steps rely on `set -euo pipefail` (or `set -uo pipefail` when a non-zero grep match
38+
count is expected and handled explicitly) — preserve these when editing shell steps.
39+
- Any new workflow or action added here should get a corresponding one- or two-line entry in `README.md`
40+
under the relevant "Actions" or "Workflows" section.
41+
- Since this repo's own workflows are linted by `github-actions-linter.yml`, run that lint mentally (or via
42+
`zizmor` locally if available) against any new/changed workflow before considering the change done.

‎README.md‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# DOSS GitHub Actions
2+
3+
GitHub actions and workflows maintained by the CX [cx-sec-pub team](https://github.com/orgs/Checkmarx/teams/cx-sec-pub).
4+
5+
## Actions
6+
7+
TBD
8+
9+
## Workflows
10+
11+
***github-actions-linter***
12+
13+
This workflow runs style and security checks against a repo's GitHub workflow and action definitions.
14+
15+
***trufflehog-secret-scan***
16+
17+
This workflow scans a pull request's commits for secrets using TruffleHog, posting/updating a single PR comment with the findings and blocking the merge if a verified (live) secret is found.

0 commit comments

Comments
 (0)