Skip to content

Commit 8b19725

Browse files
WilliamBerryiiiBill Berry
andauthored
feat(workflows): add gitleaks binary-based secret scanning as PR gate (#734)
## Description This PR introduces **gitleaks** as a binary-based secret scanning gate across both CI pipelines, complementing GitHub's native secret scanning with a locally verified, SHA-pinned binary that produces SARIF output for the Security tab. > The branch addresses #260 by creating a reusable workflow that downloads, verifies, and runs gitleaks as part of every PR validation and main-branch push. ### Gitleaks Reusable Workflow A new *gitleaks-scan.yml* reusable workflow was created with three configurable inputs: `soft-fail`, `upload-sarif`, and `upload-artifact`. The workflow downloads gitleaks **v8.30.0**, verifies the tarball against a pinned SHA256 checksum, and runs a full-repository scan in git mode. SARIF output at `logs/gitleaks-results.sarif` integrates with GitHub's Security tab via `github/codeql-action/upload-sarif`. - Exit code handling distinguishes clean scans (0), detected secrets (1, respects `soft-fail`), and unexpected errors - A `.gitleaksignore` file is respected when present for suppressing known findings - Job summary table displays scan status directly in the Actions UI - All third-party actions are pinned to full SHA with version comments ### Pipeline Integration Both *pr-validation.yml* and *main.yml* now call the reusable workflow with `soft-fail: false` and `upload-sarif: true`. The PR pipeline disables artifact upload to reduce storage noise, while the main pipeline retains artifacts for 90 days. The main pipeline's aggregation gate job depends on `gitleaks-scan` completing successfully. ### Threat Model Documentation The security threat model was updated to reflect expanded coverage. VM-2 now references both GitHub native scanning and the gitleaks PR gate. The workflow coverage table includes a new `main.yml` row and lists gitleaks as a security check for both pipelines. ## Related Issue(s) Closes #260 ## Type of Change Select all that apply: **Code & Documentation:** - [ ] Bug fix (non-breaking change fixing an issue) - [x] New feature (non-breaking change adding functionality) - [ ] Breaking change (fix or feature causing existing functionality to change) - [x] Documentation update **Infrastructure & Configuration:** - [x] GitHub Actions workflow - [ ] Linting configuration (markdown, PowerShell, etc.) - [ ] Security configuration - [ ] DevContainer configuration - [ ] Dependency update **AI Artifacts:** - [ ] Reviewed contribution with `prompt-builder` agent and addressed all feedback - [ ] Copilot instructions (`.github/instructions/*.instructions.md`) - [ ] Copilot prompt (`.github/prompts/*.prompt.md`) - [ ] Copilot agent (`.github/agents/*.agent.md`) - [ ] Copilot skill (`.github/skills/*/SKILL.md`) > **Note for AI Artifact Contributors**: > > - **Agents**: Research, indexing/referencing other project (using standard VS Code GitHub Copilot/MCP tools), planning, and general implementation agents likely already exist. Review `.github/agents/` before creating new ones. > - **Skills**: Must include both bash and PowerShell scripts. See [Skills](../docs/contributing/skills.md). > - **Model Versions**: Only contributions targeting the **latest Anthropic and OpenAI models** will be accepted. Older model versions (e.g., GPT-3.5, Claude 3) will be rejected. > - See [Agents Not Accepted](../docs/contributing/custom-agents.md#agents-not-accepted) and [Model Version Requirements](../docs/contributing/ai-artifacts-common.md#model-version-requirements). **Other:** - [ ] Script/automation (`.ps1`, `.sh`, `.py`) - [ ] Other (please describe): ## Sample Prompts (for AI Artifact Contributions) <!-- If you checked any boxes under "AI Artifacts" above, provide a sample prompt showing how to use your contribution --> <!-- Delete this section if not applicable --> **User Request:** <!-- What natural language request would trigger this agent/prompt/instruction? --> **Execution Flow:** <!-- Step-by-step: what happens when invoked? Include tool usage, decision points --> **Output Artifacts:** <!-- What files/content are created? Show first 10-20 lines as preview --> **Success Indicators:** <!-- How does user know it worked correctly? What validation should they perform? --> For detailed contribution requirements, see: - **Common Standards**: [docs/contributing/ai-artifacts-common.md](../docs/contributing/ai-artifacts-common.md) - Shared standards for XML blocks, markdown quality, RFC 2119, validation, and testing - **Agents**: [docs/contributing/custom-agents.md](../docs/contributing/custom-agents.md) - Agent configurations with tools and behavior patterns - **Prompts**: [docs/contributing/prompts.md](../docs/contributing/prompts.md) - Workflow-specific guidance with template variables - **Instructions**: [docs/contributing/instructions.md](../docs/contributing/instructions.md) - Technology-specific standards with glob patterns - **Skills**: [docs/contributing/skills.md](../docs/contributing/skills.md) - Task execution utilities with cross-platform scripts ## Testing ### Automated Validations <!-- Checkbox results populated after Step 7 validation run --> | Command | Status | |---------|--------| | `npm run lint:md` | Passed | | `npm run spell-check` | Passed | | `npm run lint:frontmatter` | Passed | | `npm run validate:skills` | Passed | | `npm run lint:md-links` | Passed (12 pre-existing broken links in unrelated files) | | `npm run lint:ps` | Passed | | `npm run lint:yaml` | Passed | | `npm run lint:version-consistency` | Passed | | `npm run plugin:generate` | Passed (no diff) | ### Diff-Based Assessment - All third-party actions verified as SHA-pinned with version comments - `persist-credentials: false` confirmed on checkout step - Permissions scoped to minimum required (`contents: read`, `security-events: write`) - Binary download verified via SHA256 checksum before execution - No secrets or sensitive data present in the diff ### Manual Testing Manual testing was not performed. Workflow execution validates on PR creation and main-branch push. ## Checklist ### Required Checks - [x] Documentation is updated (if applicable) - [x] Files follow existing naming conventions - [x] Changes are backwards compatible (if applicable) - [ ] Tests added for new functionality (N/A — GitHub Actions workflows are validated by pipeline execution) ### AI Artifact Contributions <!-- If contributing an agent, prompt, instruction, or skill, complete these checks --> - [ ] Used `/prompt-analyze` to review contribution - [ ] Addressed all feedback from `prompt-builder` review - [ ] Verified contribution follows common standards and type-specific requirements ### Required Automated Checks The following validation commands must pass before merging: - [x] Markdown linting: `npm run lint:md` - [x] Spell checking: `npm run spell-check` - [x] Frontmatter validation: `npm run lint:frontmatter` - [x] Skill structure validation: `npm run validate:skills` - [x] Link validation: `npm run lint:md-links` - [x] PowerShell analysis: `npm run lint:ps` - [x] YAML/actionlint validation: `npm run lint:yaml` - [x] Action version consistency: `npm run lint:version-consistency` - [x] Plugin freshness: `npm run plugin:generate` ## Security Considerations <!-- ⚠️ WARNING: Do not commit sensitive information such as API keys, passwords, or personal data --> - [x] This PR does not contain any sensitive or NDA information - [ ] Any new dependencies have been reviewed for security issues (N/A — no dependency changes) - [ ] Security-related scripts follow the principle of least privilege (N/A — no security scripts modified) ## Additional Notes The gitleaks workflow respects a `.gitleaksignore` file when present, allowing suppression of known findings. No `.gitleaksignore` currently exists in the repository. If pre-existing secrets are detected during the first pipeline run, create a `.gitleaksignore` at the repository root with the finding fingerprints to suppress them. --------- Co-authored-by: Bill Berry <wbery@microsoft.com>
1 parent 88f9ddb commit 8b19725

File tree

5 files changed

+208
-11
lines changed

5 files changed

+208
-11
lines changed
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
name: Gitleaks Secret Scan
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
soft-fail:
7+
description: 'Whether to continue on secret detection'
8+
required: false
9+
type: boolean
10+
default: false
11+
upload-sarif:
12+
description: 'Whether to upload SARIF results to Security tab'
13+
required: false
14+
type: boolean
15+
default: false
16+
upload-artifact:
17+
description: 'Whether to upload results as artifact'
18+
required: false
19+
type: boolean
20+
default: true
21+
log-opts:
22+
description: 'Extra git log options passed to gitleaks (e.g. a revision range to scope the scan)'
23+
required: false
24+
type: string
25+
default: ''
26+
27+
permissions:
28+
contents: read
29+
30+
jobs:
31+
scan:
32+
name: Gitleaks Secret Scan
33+
runs-on: ubuntu-latest
34+
permissions:
35+
contents: read
36+
security-events: write
37+
steps:
38+
- name: Checkout code
39+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2
40+
with:
41+
persist-credentials: false
42+
fetch-depth: 0
43+
44+
- name: Download and verify gitleaks
45+
shell: bash
46+
run: |
47+
set -euo pipefail
48+
49+
GITLEAKS_VERSION="8.30.0"
50+
GITLEAKS_SHA256="79a3ab579b53f71efd634f3aaf7e04a0fa0cf206b7ed434638d1547a2470a66e"
51+
GITLEAKS_URL="https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
52+
GITLEAKS_TARBALL="gitleaks.tar.gz"
53+
54+
echo "Downloading gitleaks v${GITLEAKS_VERSION}..."
55+
curl -fsSL -o "${GITLEAKS_TARBALL}" "${GITLEAKS_URL}"
56+
57+
echo "Verifying SHA256 checksum..."
58+
echo "${GITLEAKS_SHA256} ${GITLEAKS_TARBALL}" | sha256sum -c -
59+
60+
echo "Extracting gitleaks binary..."
61+
tar -xzf "${GITLEAKS_TARBALL}" gitleaks
62+
chmod +x gitleaks
63+
64+
echo "Gitleaks version:"
65+
./gitleaks version
66+
67+
- name: Run gitleaks scan
68+
id: gitleaks
69+
shell: bash
70+
run: |
71+
set -euo pipefail
72+
73+
mkdir -p logs
74+
75+
SCAN_ARGS=(
76+
"git"
77+
"--report-format" "sarif"
78+
"--report-path" "logs/gitleaks-results.sarif"
79+
"--redact"
80+
"--log-level" "info"
81+
)
82+
83+
LOG_OPTS="${{ inputs.log-opts }}"
84+
if [ -n "$LOG_OPTS" ]; then
85+
SCAN_ARGS+=("--log-opts=$LOG_OPTS")
86+
echo "Scoping scan with log-opts: $LOG_OPTS"
87+
fi
88+
89+
# Respect .gitleaksignore for pre-existing secrets
90+
if [ -f ".gitleaksignore" ]; then
91+
echo "Found .gitleaksignore — known secrets will be suppressed."
92+
fi
93+
94+
echo "Running gitleaks scan..."
95+
EXIT_CODE=0
96+
./gitleaks "${SCAN_ARGS[@]}" || EXIT_CODE=$?
97+
98+
if [ "$EXIT_CODE" -eq 0 ]; then
99+
echo "No secrets detected."
100+
echo "leaks-found=false" >> "$GITHUB_OUTPUT"
101+
elif [ "$EXIT_CODE" -eq 1 ]; then
102+
echo "::warning::Gitleaks detected secrets in the repository."
103+
echo "leaks-found=true" >> "$GITHUB_OUTPUT"
104+
if [ "${{ inputs.soft-fail }}" != "true" ]; then
105+
echo "::error::Secret scanning failed. Review detected secrets in logs/gitleaks-results.sarif"
106+
exit 1
107+
fi
108+
else
109+
echo "::error::Gitleaks encountered an unexpected error (exit code: $EXIT_CODE)"
110+
exit "$EXIT_CODE"
111+
fi
112+
113+
- name: Upload SARIF to Security tab
114+
if: inputs.upload-sarif && always()
115+
uses: github/codeql-action/upload-sarif@ce729e4d353d580e6cacd6a8cf2921b72e5e310a # v3.27.0
116+
with:
117+
sarif_file: logs/gitleaks-results.sarif
118+
category: gitleaks
119+
continue-on-error: true
120+
121+
- name: Upload scan results
122+
if: inputs.upload-artifact && always()
123+
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4.4.3
124+
with:
125+
name: gitleaks-results
126+
path: logs/gitleaks-results.sarif
127+
retention-days: 90
128+
129+
- name: Add job summary
130+
if: always()
131+
shell: bash
132+
run: |
133+
LEAKS_FOUND="${{ steps.gitleaks.outputs.leaks-found }}"
134+
if [ "$LEAKS_FOUND" = "true" ]; then
135+
STATUS="⚠️ Secrets Detected"
136+
else
137+
STATUS="✅ No Secrets Found"
138+
fi
139+
140+
cat >> "$GITHUB_STEP_SUMMARY" <<EOF
141+
## Gitleaks Secret Scan Results
142+
143+
| Metric | Value |
144+
|--------|-------|
145+
| Status | $STATUS |
146+
147+
EOF

.github/workflows/main.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,17 @@ jobs:
5151
upload-sarif: true
5252
upload-artifact: true
5353

54+
gitleaks-scan:
55+
name: Gitleaks Secret Scan
56+
uses: ./.github/workflows/gitleaks-scan.yml
57+
permissions:
58+
contents: read
59+
security-events: write
60+
with:
61+
soft-fail: false
62+
upload-sarif: true
63+
upload-artifact: true
64+
5465
pester-tests:
5566
name: PowerShell Tests
5667
uses: ./.github/workflows/pester-tests.yml
@@ -69,6 +80,7 @@ jobs:
6980
- markdown-lint
7081
- table-format
7182
- dependency-pinning-scan
83+
- gitleaks-scan
7284
- pester-tests
7385
runs-on: ubuntu-latest
7486
outputs:

.github/workflows/pr-validation.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,18 @@ jobs:
134134
upload-sarif: true
135135
upload-artifact: false
136136

137+
gitleaks-scan:
138+
name: Gitleaks Secret Scan
139+
uses: ./.github/workflows/gitleaks-scan.yml
140+
permissions:
141+
contents: read
142+
security-events: write
143+
with:
144+
soft-fail: false
145+
upload-sarif: true
146+
upload-artifact: false
147+
log-opts: '${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}'
148+
137149
npm-audit:
138150
name: npm Security Audit
139151
runs-on: ubuntu-latest

.gitleaksignore

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# False positives: fake GitHub PAT 'ghp_test123456789' used as test fixture data
2+
# in Pester security test files for SHA pinning and staleness validation.
3+
# Rule: generic-api-key
4+
5+
# Test-SHAStaleness.Tests.ps1
6+
1c9634d8334ebe8e88513f9a531b49d03590c307:scripts/tests/security/Test-SHAStaleness.Tests.ps1:generic-api-key:33
7+
1c9634d8334ebe8e88513f9a531b49d03590c307:scripts/tests/security/Test-SHAStaleness.Tests.ps1:generic-api-key:38
8+
1c9634d8334ebe8e88513f9a531b49d03590c307:scripts/tests/security/Test-SHAStaleness.Tests.ps1:generic-api-key:55
9+
1c9634d8334ebe8e88513f9a531b49d03590c307:scripts/tests/security/Test-SHAStaleness.Tests.ps1:generic-api-key:105
10+
11+
# Update-ActionSHAPinning.Tests.ps1
12+
1c9634d8334ebe8e88513f9a531b49d03590c307:scripts/tests/security/Update-ActionSHAPinning.Tests.ps1:generic-api-key:60
13+
1c9634d8334ebe8e88513f9a531b49d03590c307:scripts/tests/security/Update-ActionSHAPinning.Tests.ps1:generic-api-key:92
14+
1c9634d8334ebe8e88513f9a531b49d03590c307:scripts/tests/security/Update-ActionSHAPinning.Tests.ps1:generic-api-key:143
15+
3a11b26086c43b9551e6a7b666ffb6aa5dd8a8d7:scripts/tests/security/Update-ActionSHAPinning.Tests.ps1:generic-api-key:93
16+
3a11b26086c43b9551e6a7b666ffb6aa5dd8a8d7:scripts/tests/security/Update-ActionSHAPinning.Tests.ps1:generic-api-key:127
17+
3a11b26086c43b9551e6a7b666ffb6aa5dd8a8d7:scripts/tests/security/Update-ActionSHAPinning.Tests.ps1:generic-api-key:193
18+
4ef6d20475e42b753c29846aa28489843e70a8ed:scripts/tests/security/Update-ActionSHAPinning.Tests.ps1:generic-api-key:256
19+
4ef6d20475e42b753c29846aa28489843e70a8ed:scripts/tests/security/Update-ActionSHAPinning.Tests.ps1:generic-api-key:488
20+
a02335e7c98e5f2e22b1921442e99f01c6781a19:scripts/tests/security/Update-ActionSHAPinning.Tests.ps1:generic-api-key:280
21+
a02335e7c98e5f2e22b1921442e99f01c6781a19:scripts/tests/security/Update-ActionSHAPinning.Tests.ps1:generic-api-key:511
22+
f7c9b9a6f776e6403f393adf90a67127f3fc3405:scripts/tests/security/Update-ActionSHAPinning.Tests.ps1:generic-api-key:791
23+
f7c9b9a6f776e6403f393adf90a67127f3fc3405:scripts/tests/security/Update-ActionSHAPinning.Tests.ps1:generic-api-key:869
24+
6b84a8e49193d266411df9e4b8e8b1be2369eed2:scripts/tests/security/Update-ActionSHAPinning.Tests.ps1:generic-api-key:792
25+
6b84a8e49193d266411df9e4b8e8b1be2369eed2:scripts/tests/security/Update-ActionSHAPinning.Tests.ps1:generic-api-key:874

docs/security/threat-model.md

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -723,11 +723,11 @@ These threats address ethical and responsible AI considerations aligned with Mic
723723

724724
### Vulnerability Management Controls
725725

726-
| ID | Control | Implementation | Validates Against |
727-
|------|---------------------------------|----------------------------|-------------------|
728-
| VM-1 | Coordinated Disclosure | SECURITY.md | I-1 |
729-
| VM-2 | Secret Scanning | GitHub native | I-1, I-2 |
730-
| VM-3 | Credential Persistence Disabled | persist-credentials: false | I-1, E-1 |
726+
| ID | Control | Implementation | Validates Against |
727+
|------|---------------------------------|-----------------------------------------------------|-------------------|
728+
| VM-1 | Coordinated Disclosure | SECURITY.md | I-1 |
729+
| VM-2 | Secret Scanning | GitHub native, gitleaks PR gate (gitleaks-scan.yml) | I-1, I-2 |
730+
| VM-3 | Credential Persistence Disabled | persist-credentials: false | I-1, E-1 |
731731

732732
## Assurance Argument
733733

@@ -863,12 +863,13 @@ HVE Core documents integrations with Model Context Protocol servers. This sectio
863863

864864
### Validation Workflow Coverage
865865

866-
| Workflow | Trigger | Security Checks |
867-
|---------------------------------|--------------------|----------------------------|
868-
| pr-validation.yml | PR to main/develop | Pinning, npm audit, CodeQL |
869-
| codeql-analysis.yml | Push, PR, weekly | Static analysis |
870-
| dependency-review.yml | PR to main/develop | Vulnerability scanning |
871-
| weekly-security-maintenance.yml | Sundays 2 AM UTC | Pinning, staleness, CodeQL |
866+
| Workflow | Trigger | Security Checks |
867+
|---------------------------------|--------------------|--------------------------------------|
868+
| pr-validation.yml | PR to main/develop | Pinning, npm audit, CodeQL, gitleaks |
869+
| main.yml | Push to main | Pinning, gitleaks |
870+
| codeql-analysis.yml | Push, PR, weekly | Static analysis |
871+
| dependency-review.yml | PR to main/develop | Vulnerability scanning |
872+
| weekly-security-maintenance.yml | Sundays 2 AM UTC | Pinning, staleness, CodeQL |
872873

873874
## References
874875

0 commit comments

Comments
 (0)