Skip to content

Commit 41cb135

Browse files
committed
move run-semgrep script into composite action to facilitate calling workflow cross-repo
1 parent 35c7666 commit 41cb135

17 files changed

Lines changed: 254 additions & 415 deletions

File tree

.github/actions/run-semgrep/.npmrc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
ignore-scripts=true
2+
save-exact=true
3+
audit=true
4+
fund=false
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Changelog for run-semgrep Composite Action
2+
3+
All notable changes to the run-semgrep composite GitHub Action will be documented in this file.
4+
5+
## 1.0.0 - Initial Release
6+
7+
### Added
8+
9+
- Initial release of the reusable composite action for running Semgrep scans
10+
- Inputs are passed via environment variables
11+
- Support running on both push and pull_request events
12+
- Standardizes baseline resolution for diff scans
13+
- Outputs include scan summary, config summary, scan status, and finding counts
14+
- Designed to integrate with reviewdog for annotations
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# Run Semgrep Action
2+
3+
## 🧭 Summary
4+
5+
Runs a Semgrep scan normalizing the baseline for diff scans depending on push vs PR context. Outputs scan results and summaries for downstream steps.
6+
7+
## Scope/Limitations
8+
9+
- Supports both push and pull request events.
10+
- Requires Semgrep to be installed and available in the runner environment.
11+
- Expects environment variables for configuration (see below).
12+
13+
## 🔒 Permissions
14+
15+
The following GHA permissions are required to use this step:
16+
17+
```yaml
18+
permissions:
19+
contents: read
20+
```
21+
22+
## Dependencies
23+
24+
- `semgrep` — must be installed in the runner environment.
25+
- `node-fetch` — required Node.js dependency (see package.json).
26+
- `reviewdog` — for annotation output (optional, for downstream steps).
27+
28+
## ⚙️ Inputs
29+
30+
This action is environment-driven. The following environment variables are required:
31+
32+
| Name | Required | Description |
33+
| ------------------- | -------- | ------------------------------------------------------------------------------------------- |
34+
| `HAS_PR` | ✅ | Whether the current context has an associated PR (true/false) |
35+
| `PR_NUMBER` | ❌ | PR number if applicable |
36+
| `PR_URL` | ❌ | PR URL if applicable |
37+
| `INPUT_BASELINE` | ✅ | Baseline ref to use for diffing (e.g., origin/main) |
38+
| `GITHUB_EVENT_NAME` | ✅ | GitHub provided environment variable for event name (e.g., push, pull_request) |
39+
| `GITHUB_REF_NAME` | ✅ | GitHub provided environment variable for the branch or tag name that triggered the workflow |
40+
| `GITHUB_BASE_REF` | ❌ | GitHub provided environment variable for the base ref of a PR (if applicable) |
41+
| `GITHUB_REPOSITORY` | ✅ | GitHub provided environment variable for the repository (e.g., owner/repo) |
42+
| `GITHUB_TOKEN` | ✅ | GitHub token for API access |
43+
| `SCAN_MODE` | ✅ | 'diff' or 'full' scan mode |
44+
| `SEMGREP_CONFIG` | ✅ | Semgrep ruleset(s) to use |
45+
| `SEMGREP_TARGETS` | ✅ | Targets to scan (default: current directory) |
46+
| `FAIL_LEVEL` | ✅ | Severity level to fail on (e.g., ERROR, WARNING) |
47+
| `EXTRA_ARGS` | ❌ | Additional arguments to pass to Semgrep |
48+
49+
## 📤 Outputs
50+
51+
Along with writing files for reviewdog annotations and inputs, this action provides the following outputs:
52+
53+
| Name | Description |
54+
| -------------------- | --------------------------------------------------- |
55+
| `normalizedBaseline` | The resolved baseline ref |
56+
| `scanSummary` | Summary of findings in markdown format |
57+
| `configSummary` | Summary of scan config in markdown format |
58+
| `scanStatus` | 'success' or 'failure' based on findings/fail level |
59+
| `totalFindings` | Total number of findings |
60+
| `numErrors` | Number of ERROR severity findings |
61+
| `numWarnings` | Number of WARNING severity findings |
62+
| `numInfo` | Number of INFO severity findings |
63+
64+
## 🚀 Usage
65+
66+
Basic usage example:
67+
68+
```yaml
69+
- name: Run Semgrep
70+
id: semgrep
71+
uses: OpenSesame/core-github-actions/.github/actions/run-semgrep@actions/run-semgrep/1.0.0
72+
env:
73+
HAS_PR: ${{ env.HAS_PR }}
74+
INPUT_BASELINE: ${{ env.INPUT_BASELINE }}
75+
GITHUB_EVENT_NAME: ${{ github.event_name }}
76+
GITHUB_REF_NAME: ${{ github.ref_name }}
77+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
78+
GITHUB_REPOSITORY: ${{ github.repository }}
79+
SEMGREP_CONFIG: 'p/default'
80+
SEMGREP_TARGETS: '.'
81+
SCAN_MODE: 'full'
82+
FAIL_LEVEL: 'error'
83+
EXTRA_ARGS: ''
84+
```
85+
86+
Example outputs:
87+
88+
```yaml
89+
steps.semgrep.outputs.scanStatus
90+
steps.semgrep.outputs.totalFindings
91+
```
92+
93+
Example usage of outputs in later steps:
94+
95+
```yaml
96+
if: steps.semgrep.outputs.scanStatus == 'failure'
97+
run: echo "Semgrep scan failed at or above threshold."
98+
```
99+
100+
## 🧠 Notes
101+
102+
- This action writes a file for reviewdog annotations (`reviewdog_input.txt`).
103+
- Unit tests for the script are included in `run-semgrep.unit.test.js` (not used by the action, but kept for maintainability).
104+
105+
## Versioning
106+
107+
This action uses namespaced tags for versioning and is tracked in the CHANGELOG.
108+
109+
```text
110+
actions/run-semgrep/vX.Y.Z
111+
```
112+
113+
See the repository's versioning documentation for details on how tags are validated and created.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
name: 'Run Semgrep'
2+
description: 'Run a Semgrep scan and output results for reviewdog and future steps'
3+
runs:
4+
using: composite
5+
steps:
6+
- name: Install action dependencies
7+
shell: bash
8+
working-directory: ${{ github.action_path }}
9+
run: npm ci
10+
11+
- name: Run Semgrep Scan
12+
shell: bash
13+
working-directory: ${{ github.action_path }}
14+
run: node ${{ github.action_path }}/run-semgrep.js
File renamed without changes.
File renamed without changes.

.github/actions/run-semgrep/package-lock.json

Lines changed: 57 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "@opensesame/run-semgrep-action",
3+
"version": "1.0.0",
4+
"main": "run-semgrep.js",
5+
"private": true,
6+
"description": "Composite action to run Semgrep scan for GitHub Actions.",
7+
"dependencies": {
8+
"node-fetch": "^2.6.7"
9+
}
10+
}

scripts/gha-lib/run-semgrep.js renamed to .github/actions/run-semgrep/run-semgrep.js

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,7 @@
1-
/*
2-
* Run Semgrep scan
3-
* Normalizes baseline for diff scans depending on push vs PR context
4-
*
5-
* Expects the following environment variables:
6-
* HAS_PR - whether the current context has an associated PR (true/false)
7-
* PR_NUMBER - PR number if applicable
8-
* PR_URL - PR URL if applicable
9-
* INPUT_BASELINE - baseline ref to use for diffing (e.g., origin/main)
10-
* GITHUB_EVENT_NAME - GitHub provided environment variable for event name (e.g., push, pull_request)
11-
* GITHUB_REF - Github provided environment variable for the git ref that triggered the workflow
12-
* GITHUB_REF_NAME - GitHub provided environment variable for the branch or tag name that triggered the workflow
13-
* GITHUB_BASE_REF - GitHub provided environment variable for the base ref of a PR (if applicable)
14-
* GITHUB_REPOSITORY - GitHub provided environment variable for the repository (e.g., owner/repo)
15-
* GITHUB_TOKEN - GitHub token for API access
16-
* SCAN_MODE - 'diff' or 'full' scan mode
17-
* SEMGREP_CONFIG - Semgrep ruleset(s) to use
18-
* SEMGREP_TARGETS - Targets to scan (default: current directory)
19-
* FAIL_LEVEL - Severity level to fail on (e.g., ERROR, WARNING)
20-
* EXTRA_ARGS - Additional arguments to pass to Semgrep
21-
*
22-
* Outputs:
23-
* - Writes file for reviewdog annotations, reviewdog_input.txt
24-
* - Sets GitHub Action outputs
25-
* - normalizedBaseline - the resolved baseline ref
26-
* - totalFindings - total number of findings
27-
* - numErrors - number of ERROR severity findings
28-
* - numWarnings - number of WARNING severity findings
29-
* - numInfo - number of INFO severity findings
30-
* - scanSummary - summary of findings in md format
31-
* - configSummary - summary of scan config in md format
32-
* - scanStatus - 'success' or 'failure' based on findings and fail level
33-
*/
34-
351
const { spawnSync } = require('child_process');
362
const fs = require('fs');
373
const fetch = require('node-fetch');
38-
const { validateEnvVar } = require('../utils/env-helpers');
4+
const { validateEnvVar } = require('./env-helpers');
395

406
const SEMGREP_RESULTS_FILE_NAME = 'semgrep_results.json';
417
const REVIEWDOG_INPUT_FILE_NAME = 'reviewdog_input.txt';
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
const { validateEnvVar } = require('./env-helpers');
2+
3+
describe('validateEnvVar', () => {
4+
const ORIGINAL_EXIT = process.exit;
5+
const ORIGINAL_CONSOLE_ERROR = console.error;
6+
7+
beforeEach(() => {
8+
process.exit = jest.fn();
9+
console.error = jest.fn();
10+
});
11+
12+
afterEach(() => {
13+
process.exit = ORIGINAL_EXIT;
14+
console.error = ORIGINAL_CONSOLE_ERROR;
15+
});
16+
17+
it('does not exit when env var is set', () => {
18+
process.env.TEST_VAR = 'value';
19+
validateEnvVar('TEST_VAR');
20+
expect(process.exit).not.toHaveBeenCalled();
21+
expect(console.error).not.toHaveBeenCalled();
22+
delete process.env.TEST_VAR;
23+
});
24+
25+
it('exits with error when env var is not set', () => {
26+
delete process.env.TEST_VAR;
27+
validateEnvVar('TEST_VAR');
28+
expect(console.error).toHaveBeenCalledWith(
29+
'::error::Environment variable TEST_VAR is required'
30+
);
31+
expect(process.exit).toHaveBeenCalledWith(1);
32+
});
33+
});

0 commit comments

Comments
 (0)