@osfactory/har 0.52.0 → 0.54.0
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/dist/index.js +3 -3
- package/dist/templates/har-boilerplate/STAGES.md +5 -2
- package/dist/templates/har-boilerplate-cli/STAGES.md +5 -2
- package/dist/templates/har-boilerplate-ios/STAGES.md +5 -2
- package/dist/templates/plugins/semgrep/.github/workflows/semgrep.yml +39 -0
- package/dist/templates/plugins/semgrep/.har/stages/SEMGREP.md +91 -0
- package/dist/templates/plugins/semgrep/.har/stages/sast.sh +141 -0
- package/dist/templates/plugins/semgrep/README.md +57 -0
- package/dist/templates/plugins/semgrep/template.manifest.json +44 -0
- package/dist/templates/plugins/trivy/.github/workflows/trivy.yml +40 -0
- package/dist/templates/plugins/trivy/.har/stages/TRIVY.md +88 -0
- package/dist/templates/plugins/trivy/.har/stages/vuln-scan.sh +157 -0
- package/dist/templates/plugins/trivy/.trivyignore +9 -0
- package/dist/templates/plugins/trivy/README.md +32 -0
- package/dist/templates/plugins/trivy/template.manifest.json +44 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -69193,7 +69193,7 @@ function harnessExists(repoPath) {
|
|
|
69193
69193
|
// src/harness/plugins.ts
|
|
69194
69194
|
init_schema2();
|
|
69195
69195
|
init_stages();
|
|
69196
|
-
var PLUGIN_IDS = ["playwright", "rocketsim", "kerno", "gitleaks"];
|
|
69196
|
+
var PLUGIN_IDS = ["playwright", "rocketsim", "kerno", "gitleaks", "trivy", "semgrep"];
|
|
69197
69197
|
var PluginManifestFileSchema = external_exports.object({
|
|
69198
69198
|
src: external_exports.string().min(1),
|
|
69199
69199
|
dest: external_exports.string().min(1),
|
|
@@ -70292,8 +70292,8 @@ init_logging();
|
|
|
70292
70292
|
init_manifest();
|
|
70293
70293
|
var ADAPTATION_PROMPT_FILE = "ADAPT-PROMPT.md";
|
|
70294
70294
|
var PROFILE_HINTS = {
|
|
70295
|
-
default: "Web app profile (SaaS/full-stack) \u2014 Docker Compose for shared infra (HARNESS_INFRA_SERVICES), PM2 for the primary application only, git worktree per agent slot by default. Launch provisions toolchain via harness.env (HARNESS_ECOSYSTEM, HARNESS_INSTALL_CMD) and writes paths to .env.agent.<id>. Identify the primary app agents modify; run supporting services shared.",
|
|
70296
|
-
cli: "CLI/library profile (typical SWE-bench) \u2014 no PM2. Optional Docker Compose via HARNESS_INFRA_SERVICES. Git worktree by default. Launch provisions toolchain declaratively (HARNESS_ECOSYSTEM auto-detects common ecosystems); verify must use resolved tool paths from .env.agent.<id>, never hardcoded interpreter or package-manager paths.",
|
|
70295
|
+
default: "Web app profile (SaaS/full-stack) \u2014 Docker Compose for shared infra (HARNESS_INFRA_SERVICES), PM2 for the primary application only, git worktree per agent slot by default. Launch provisions toolchain via harness.env (HARNESS_ECOSYSTEM, HARNESS_INSTALL_CMD) and writes paths to .env.agent.<id>. Identify the primary app agents modify; run supporting services shared. Install the Semgrep plugin (har env add-plugin semgrep) when the repo needs a SAST verification stage.",
|
|
70296
|
+
cli: "CLI/library profile (typical SWE-bench) \u2014 no PM2. Optional Docker Compose via HARNESS_INFRA_SERVICES. Git worktree by default. Launch provisions toolchain declaratively (HARNESS_ECOSYSTEM auto-detects common ecosystems); verify must use resolved tool paths from .env.agent.<id>, never hardcoded interpreter or package-manager paths. Install the Semgrep plugin (har env add-plugin semgrep) when the repo needs a SAST verification stage.",
|
|
70297
70297
|
ios: "iOS mobile app profile \u2014 xcodebuild + iOS Simulator in an isolated git worktree. Set HARNESS_XCODE_SCHEME, workspace/project, HARNESS_SIMULATOR_NAME, HARNESS_BUNDLE_ID. Launch writes XCODEBUILD_BIN to .env.agent.<id>; verify uses it. Install RocketSim plugin (har env add-plugin rocketsim) for user-flow validation."
|
|
70298
70298
|
};
|
|
70299
70299
|
function loadTemplate(name) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# HAR Stages — authoring guide
|
|
2
2
|
|
|
3
3
|
Stages are the harness's single vocabulary for runnable checks and lifecycle
|
|
4
|
-
actions. Everything — shipped plugins (`playwright`, `rocketsim`, `kerno`, `gitleaks`), your
|
|
4
|
+
actions. Everything — shipped plugins (`playwright`, `rocketsim`, `kerno`, `gitleaks`, `trivy`, `semgrep`), your
|
|
5
5
|
project's test/lint commands, bespoke validation scripts — registers in
|
|
6
6
|
`.har/stages.json` with the same schema, and agents interact with stages only
|
|
7
7
|
through that registry (CLI `har env verify`, MCP `har_run_stage` /
|
|
@@ -91,6 +91,9 @@ committed.
|
|
|
91
91
|
|
|
92
92
|
`har env add-plugin --list` shows available plugins; `har env add-plugin
|
|
93
93
|
playwright` (web), `har env add-plugin rocketsim` (iOS), or `har env
|
|
94
|
-
add-plugin kerno` (backend)
|
|
94
|
+
add-plugin kerno` (backend), `har env add-plugin gitleaks` (secrets scanning, any
|
|
95
|
+
stack), or `har env add-plugin trivy` (dependency + IaC security scan, any stack),
|
|
96
|
+
or `har env add-plugin semgrep` (SAST, any stack)
|
|
97
|
+
installs one. A
|
|
95
98
|
plugin is just packaging: it copies files, merges `package.json` fragments,
|
|
96
99
|
and registers stages through the exact same registry as `add-stage --custom`.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# HAR Stages — authoring guide
|
|
2
2
|
|
|
3
3
|
Stages are the harness's single vocabulary for runnable checks and lifecycle
|
|
4
|
-
actions. Everything — shipped plugins (`playwright`, `rocketsim`, `kerno`, `gitleaks`), your
|
|
4
|
+
actions. Everything — shipped plugins (`playwright`, `rocketsim`, `kerno`, `gitleaks`, `trivy`, `semgrep`), your
|
|
5
5
|
project's test/lint commands, bespoke validation scripts — registers in
|
|
6
6
|
`.har/stages.json` with the same schema, and agents interact with stages only
|
|
7
7
|
through that registry (CLI `har env verify`, MCP `har_run_stage` /
|
|
@@ -91,6 +91,9 @@ committed.
|
|
|
91
91
|
|
|
92
92
|
`har env add-plugin --list` shows available plugins; `har env add-plugin
|
|
93
93
|
playwright` (web), `har env add-plugin rocketsim` (iOS), or `har env
|
|
94
|
-
add-plugin kerno` (backend)
|
|
94
|
+
add-plugin kerno` (backend), `har env add-plugin gitleaks` (secrets scanning, any
|
|
95
|
+
stack), or `har env add-plugin trivy` (dependency + IaC security scan, any stack),
|
|
96
|
+
or `har env add-plugin semgrep` (SAST, any stack)
|
|
97
|
+
installs one. A
|
|
95
98
|
plugin is just packaging: it copies files, merges `package.json` fragments,
|
|
96
99
|
and registers stages through the exact same registry as `add-stage --custom`.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# HAR Stages — authoring guide
|
|
2
2
|
|
|
3
3
|
Stages are the harness's single vocabulary for runnable checks and lifecycle
|
|
4
|
-
actions. Everything — shipped plugins (`playwright`, `rocketsim`, `kerno`, `gitleaks`), your
|
|
4
|
+
actions. Everything — shipped plugins (`playwright`, `rocketsim`, `kerno`, `gitleaks`, `trivy`, `semgrep`), your
|
|
5
5
|
project's test/lint commands, bespoke validation scripts — registers in
|
|
6
6
|
`.har/stages.json` with the same schema, and agents interact with stages only
|
|
7
7
|
through that registry (CLI `har env verify`, MCP `har_run_stage` /
|
|
@@ -91,6 +91,9 @@ committed.
|
|
|
91
91
|
|
|
92
92
|
`har env add-plugin --list` shows available plugins; `har env add-plugin
|
|
93
93
|
playwright` (web), `har env add-plugin rocketsim` (iOS), or `har env
|
|
94
|
-
add-plugin kerno` (backend)
|
|
94
|
+
add-plugin kerno` (backend), `har env add-plugin gitleaks` (secrets scanning, any
|
|
95
|
+
stack), or `har env add-plugin trivy` (dependency + IaC security scan, any stack),
|
|
96
|
+
or `har env add-plugin semgrep` (SAST, any stack)
|
|
97
|
+
installs one. A
|
|
95
98
|
plugin is just packaging: it copies files, merges `package.json` fragments,
|
|
96
99
|
and registers stages through the exact same registry as `add-stage --custom`.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Semgrep SAST — official recipe (https://semgrep.dev/docs/semgrep-ci/sample-ci-configs)
|
|
2
|
+
#
|
|
3
|
+
# With a SEMGREP_APP_TOKEN secret (Semgrep AppSec Platform → Settings), `semgrep ci`
|
|
4
|
+
# publishes findings to the AppSec Platform — the org-level evidence layer that
|
|
5
|
+
# compliance tools like Vanta ingest natively:
|
|
6
|
+
# https://help.vanta.com/en/articles/15705377-connecting-vanta-semgrep
|
|
7
|
+
# Without the token, this falls back to a Community Edition scan that still blocks
|
|
8
|
+
# the PR on findings but publishes nothing.
|
|
9
|
+
name: Semgrep
|
|
10
|
+
|
|
11
|
+
on:
|
|
12
|
+
pull_request: {}
|
|
13
|
+
push:
|
|
14
|
+
branches: ["main", "master"]
|
|
15
|
+
workflow_dispatch: {}
|
|
16
|
+
schedule:
|
|
17
|
+
# Daily scan so new registry rules surface without a code change
|
|
18
|
+
- cron: '17 5 * * *'
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
semgrep:
|
|
22
|
+
name: semgrep/ci
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
container:
|
|
25
|
+
image: semgrep/semgrep
|
|
26
|
+
if: (github.actor != 'dependabot[bot]')
|
|
27
|
+
steps:
|
|
28
|
+
# Pinned to a SHA so semgrep's own mutable-action-tag rule stays quiet
|
|
29
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
30
|
+
- name: Run Semgrep
|
|
31
|
+
env:
|
|
32
|
+
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
|
|
33
|
+
run: |
|
|
34
|
+
if [ -n "$SEMGREP_APP_TOKEN" ]; then
|
|
35
|
+
semgrep ci
|
|
36
|
+
else
|
|
37
|
+
echo "SEMGREP_APP_TOKEN not set — running Community Edition scan (no platform/Vanta reporting)"
|
|
38
|
+
semgrep scan --config auto --error
|
|
39
|
+
fi
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Semgrep SAST Stage
|
|
2
|
+
|
|
3
|
+
The `sast` stage scans the agent's session worktree with [Semgrep](https://semgrep.dev)
|
|
4
|
+
and fails verification when blocking findings exist. Full verify (`verify --full`)
|
|
5
|
+
runs it automatically once it is listed in `verificationStages`.
|
|
6
|
+
|
|
7
|
+
## Running
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Whole worktree
|
|
11
|
+
./.har/stages/sast.sh 1
|
|
12
|
+
|
|
13
|
+
# Only specific paths
|
|
14
|
+
./.har/stages/sast.sh 1 src/ lib/auth.py
|
|
15
|
+
|
|
16
|
+
# Included in full verify
|
|
17
|
+
./.har/verify.sh 1 --full
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Reports land under `.har/artifacts/sast/` in the **main repo root** (not the worktree):
|
|
21
|
+
|
|
22
|
+
| File | Contents |
|
|
23
|
+
|------|----------|
|
|
24
|
+
| `semgrep.json` | Full findings report (machine-readable) |
|
|
25
|
+
| `semgrep.sarif` | SARIF report — upload to GitHub code scanning if desired |
|
|
26
|
+
| `scan.log` | Semgrep's own text output for the run |
|
|
27
|
+
|
|
28
|
+
Exit codes pass through from Semgrep: `0` clean, `1` findings, `>=2` scan error.
|
|
29
|
+
|
|
30
|
+
## Choosing rules
|
|
31
|
+
|
|
32
|
+
The stage runs `semgrep scan --config "$HARNESS_SEMGREP_CONFIG"` (default `auto`).
|
|
33
|
+
Set the variable in `.har/harness.env` to pin behavior:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# .har/harness.env
|
|
37
|
+
export HARNESS_SEMGREP_CONFIG="p/ci" # curated low-noise ruleset
|
|
38
|
+
# export HARNESS_SEMGREP_CONFIG="p/security-audit"
|
|
39
|
+
# export HARNESS_SEMGREP_CONFIG=".semgrep/" # local rules only (works offline)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Honest notes about `--config auto` and registry rulesets (`p/...`):
|
|
43
|
+
|
|
44
|
+
- They **fetch rules from the Semgrep registry**, so the stage needs network access.
|
|
45
|
+
- Semgrep **sends pseudonymized metrics** to semgrep.dev when registry configs are
|
|
46
|
+
used (this cannot be disabled for registry configs). For fully offline, metric-free
|
|
47
|
+
scans, commit local rule files and point `HARNESS_SEMGREP_CONFIG` at them.
|
|
48
|
+
- `auto` picks rulesets based on the languages it detects — convenient, but pin a
|
|
49
|
+
named ruleset (`p/ci`) when you want reproducible results.
|
|
50
|
+
|
|
51
|
+
## Tuning noise
|
|
52
|
+
|
|
53
|
+
- Semgrep respects `.gitignore` by default; add a [`.semgrepignore`](https://semgrep.dev/docs/ignoring-files-folders-code)
|
|
54
|
+
file for extra exclusions (vendored code, generated files, fixtures).
|
|
55
|
+
- The stage always passes `--exclude .har` — harness scaffolding is not your
|
|
56
|
+
project's code and semgrep cannot parse some of its bash.
|
|
57
|
+
- Suppress a single finding with a trailing `# nosemgrep: <rule-id>` comment —
|
|
58
|
+
prefer rule-scoped suppressions over blanket `# nosemgrep`.
|
|
59
|
+
- Write repo-specific rules under `.semgrep/` and add the directory to your config.
|
|
60
|
+
|
|
61
|
+
## CI and compliance (Vanta)
|
|
62
|
+
|
|
63
|
+
Of the security plugins, this one has the strongest compliance story. The optional
|
|
64
|
+
workflow `.github/workflows/semgrep.yml` runs `semgrep ci` in the official
|
|
65
|
+
`semgrep/semgrep` container. With a `SEMGREP_APP_TOKEN` secret (generated in
|
|
66
|
+
Semgrep AppSec Platform → Settings), findings publish to the
|
|
67
|
+
[Semgrep AppSec Platform](https://semgrep.dev/login) — which has a
|
|
68
|
+
[native Vanta integration](https://help.vanta.com/en/articles/15705377-connecting-vanta-semgrep):
|
|
69
|
+
Vanta pulls findings, projects, and scan history from Semgrep automatically as
|
|
70
|
+
compliance evidence.
|
|
71
|
+
|
|
72
|
+
Division of labor:
|
|
73
|
+
|
|
74
|
+
- **This local stage** is the shift-left layer — agents catch findings *before*
|
|
75
|
+
code lands on the default branch. Local runs are invisible to Vanta (no noise).
|
|
76
|
+
- **The CI run** (`semgrep ci` on the default branch and PRs) is the org-level
|
|
77
|
+
evidence layer Vanta reads.
|
|
78
|
+
|
|
79
|
+
Without a token, the workflow falls back to `semgrep scan --config auto --error`
|
|
80
|
+
(Community Edition — CI still blocks on findings, but nothing publishes to the
|
|
81
|
+
platform and there is no Vanta evidence).
|
|
82
|
+
|
|
83
|
+
## Adapting per repo
|
|
84
|
+
|
|
85
|
+
- Pin `HARNESS_SEMGREP_CONFIG` to the ruleset your team standardizes on.
|
|
86
|
+
- Add `.semgrepignore` entries for paths that produce noise.
|
|
87
|
+
- If findings should not block agents yet, keep `sast` out of `verificationStages`
|
|
88
|
+
in `.har/stages.json` and run it manually while you triage the baseline.
|
|
89
|
+
- The stage summarizes reports with `node`; it assumes a Node.js toolchain is
|
|
90
|
+
available (true for HAR-provisioned slots on node ecosystems — on other stacks,
|
|
91
|
+
ensure `node` is on PATH or adapt the summary block).
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Semgrep SAST scan for an agent slot.
|
|
3
|
+
# Scans the session worktree with Semgrep and writes JSON + SARIF reports.
|
|
4
|
+
# Outputs JSON to stdout, human-readable progress to stderr.
|
|
5
|
+
#
|
|
6
|
+
# Usage: ./.har/stages/sast.sh <agent-id> [paths...]
|
|
7
|
+
# paths (optional): scan only these paths instead of the whole worktree
|
|
8
|
+
#
|
|
9
|
+
# Config: HARNESS_SEMGREP_CONFIG (default "auto") — any semgrep --config value,
|
|
10
|
+
# e.g. "p/ci", "p/security-audit", or a local rules file/dir. Set it in
|
|
11
|
+
# .har/harness.env to pin rulesets. Registry configs (auto, p/...) need
|
|
12
|
+
# network access and send pseudonymized metrics to semgrep.dev.
|
|
13
|
+
#
|
|
14
|
+
# Prerequisite: ./.har/launch.sh <agent-id> AND the semgrep CLI installed
|
|
15
|
+
# See: ./.har/stages/SEMGREP.md for the full adaptation guide.
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
19
|
+
HARNESS_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
20
|
+
REPO_ROOT="$(cd "$HARNESS_DIR/.." && pwd)"
|
|
21
|
+
# agent-slot.sh expects SCRIPT_DIR to be .har/ (slot registry lives there)
|
|
22
|
+
SCRIPT_DIR="$HARNESS_DIR"
|
|
23
|
+
|
|
24
|
+
# shellcheck source=/dev/null
|
|
25
|
+
source "$HARNESS_DIR/harness.env"
|
|
26
|
+
# shellcheck source=/dev/null
|
|
27
|
+
source "$HARNESS_DIR/agent-slot.sh"
|
|
28
|
+
|
|
29
|
+
AGENT_ID="${1:?Usage: sast.sh <agent-id> [paths...]}"
|
|
30
|
+
shift
|
|
31
|
+
SCAN_PATHS=("$@")
|
|
32
|
+
|
|
33
|
+
validate_agent_id "$AGENT_ID"
|
|
34
|
+
|
|
35
|
+
log() { echo "==> [semgrep agent-$AGENT_ID] $*" >&2; }
|
|
36
|
+
|
|
37
|
+
# ── Preflight ─────────────────────────────────────────────────────────────────
|
|
38
|
+
if ! command -v semgrep >/dev/null 2>&1; then
|
|
39
|
+
echo "Error: semgrep CLI not found." >&2
|
|
40
|
+
echo " Install with one of:" >&2
|
|
41
|
+
echo " pipx install semgrep # recommended" >&2
|
|
42
|
+
echo " pip install semgrep" >&2
|
|
43
|
+
echo " brew install semgrep # may lag behind the latest release" >&2
|
|
44
|
+
echo " Then re-run: ./.har/stages/sast.sh ${AGENT_ID}" >&2
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# ── Resolve agent env ─────────────────────────────────────────────────────────
|
|
49
|
+
ENV_FILE="$(resolve_agent_env_file "$AGENT_ID" "$REPO_ROOT")" || {
|
|
50
|
+
echo "No .env.agent.${AGENT_ID} found." >&2
|
|
51
|
+
har_suggest_launch "$AGENT_ID" >&2
|
|
52
|
+
exit 1
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# .env.agent.<id> exports REPO_ROOT pointing at the worktree — keep the main
|
|
56
|
+
# checkout path so artifacts land in the main repo root, not the worktree.
|
|
57
|
+
MAIN_REPO_ROOT="$REPO_ROOT"
|
|
58
|
+
|
|
59
|
+
set -a
|
|
60
|
+
# shellcheck source=/dev/null
|
|
61
|
+
source "$ENV_FILE"
|
|
62
|
+
set +a
|
|
63
|
+
|
|
64
|
+
WORK_DIR="$(resolve_agent_work_dir "$ENV_FILE")"
|
|
65
|
+
|
|
66
|
+
# ── Artifact directory (main repo root, not the worktree) ─────────────────────
|
|
67
|
+
ARTIFACT_DIR="$MAIN_REPO_ROOT/.har/artifacts/sast"
|
|
68
|
+
mkdir -p "$ARTIFACT_DIR"
|
|
69
|
+
|
|
70
|
+
SEMGREP_CONFIG="${HARNESS_SEMGREP_CONFIG:-auto}"
|
|
71
|
+
JSON_REPORT="$ARTIFACT_DIR/semgrep.json"
|
|
72
|
+
SARIF_REPORT="$ARTIFACT_DIR/semgrep.sarif"
|
|
73
|
+
SCAN_LOG="$ARTIFACT_DIR/scan.log"
|
|
74
|
+
|
|
75
|
+
# ── Run semgrep ───────────────────────────────────────────────────────────────
|
|
76
|
+
log "Scanning $WORK_DIR (config: $SEMGREP_CONFIG)..."
|
|
77
|
+
START=$(now_ms)
|
|
78
|
+
|
|
79
|
+
set +e
|
|
80
|
+
(
|
|
81
|
+
cd "$WORK_DIR" &&
|
|
82
|
+
semgrep scan \
|
|
83
|
+
--config "$SEMGREP_CONFIG" \
|
|
84
|
+
--error \
|
|
85
|
+
--exclude .har \
|
|
86
|
+
--json-output "$JSON_REPORT" \
|
|
87
|
+
--sarif-output "$SARIF_REPORT" \
|
|
88
|
+
${SCAN_PATHS[@]+"${SCAN_PATHS[@]}"}
|
|
89
|
+
) >"$SCAN_LOG" 2>&1
|
|
90
|
+
SEMGREP_EXIT=$?
|
|
91
|
+
set -e
|
|
92
|
+
|
|
93
|
+
END=$(now_ms)
|
|
94
|
+
TOTAL_MS=$(( END - START ))
|
|
95
|
+
|
|
96
|
+
# Surface the scan tail so failures are diagnosable without opening the log
|
|
97
|
+
tail -20 "$SCAN_LOG" | sed 's/^/ /' >&2
|
|
98
|
+
|
|
99
|
+
# semgrep exit codes: 0 = clean, 1 = findings (--error), >=2 = scan error
|
|
100
|
+
if [ "$SEMGREP_EXIT" = "0" ]; then
|
|
101
|
+
STATUS="pass"
|
|
102
|
+
log "✓ No blocking findings (${TOTAL_MS}ms)"
|
|
103
|
+
elif [ "$SEMGREP_EXIT" = "1" ]; then
|
|
104
|
+
STATUS="fail"
|
|
105
|
+
log "✗ Semgrep reported findings (${TOTAL_MS}ms) — see $JSON_REPORT"
|
|
106
|
+
else
|
|
107
|
+
STATUS="fail"
|
|
108
|
+
log "✗ Semgrep scan errored with exit code $SEMGREP_EXIT (${TOTAL_MS}ms) — see $SCAN_LOG"
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
# ── Output ────────────────────────────────────────────────────────────────────
|
|
112
|
+
node -e "
|
|
113
|
+
const fs = require('fs');
|
|
114
|
+
let findings = null;
|
|
115
|
+
let errors = null;
|
|
116
|
+
try {
|
|
117
|
+
const report = JSON.parse(fs.readFileSync('$JSON_REPORT', 'utf8'));
|
|
118
|
+
findings = report.results ? report.results.length : 0;
|
|
119
|
+
errors = report.errors ? report.errors.length : 0;
|
|
120
|
+
} catch { /* report missing when the scan itself errored */ }
|
|
121
|
+
const out = {
|
|
122
|
+
status: '$STATUS',
|
|
123
|
+
stageId: 'sast',
|
|
124
|
+
kind: 'test',
|
|
125
|
+
agent_id: $AGENT_ID,
|
|
126
|
+
total_ms: $TOTAL_MS,
|
|
127
|
+
exit_code: $SEMGREP_EXIT,
|
|
128
|
+
config: '$SEMGREP_CONFIG',
|
|
129
|
+
findings,
|
|
130
|
+
scan_errors: errors,
|
|
131
|
+
artifacts: [
|
|
132
|
+
{ path: '.har/artifacts/sast', kind: 'directory' },
|
|
133
|
+
{ path: '.har/artifacts/sast/semgrep.json', kind: 'report' }
|
|
134
|
+
]
|
|
135
|
+
};
|
|
136
|
+
process.stdout.write(JSON.stringify(out, null, 2) + '\n');
|
|
137
|
+
"
|
|
138
|
+
|
|
139
|
+
if [ "$SEMGREP_EXIT" != "0" ]; then
|
|
140
|
+
exit "$SEMGREP_EXIT"
|
|
141
|
+
fi
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Semgrep plugin
|
|
2
|
+
|
|
3
|
+
Add a Semgrep SAST stage to your harness so agents catch security findings
|
|
4
|
+
before code lands.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
har env add-plugin semgrep
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## What this installs
|
|
11
|
+
|
|
12
|
+
| File | Purpose |
|
|
13
|
+
|------|---------|
|
|
14
|
+
| `.har/stages/sast.sh` | Stage runner — scans the session worktree with Semgrep |
|
|
15
|
+
| `.har/stages/SEMGREP.md` | Adaptation guide: rulesets, noise tuning, CI/Vanta story |
|
|
16
|
+
| `.github/workflows/semgrep.yml` | Optional CI workflow (`--skip-ci` to omit) — official `semgrep ci` recipe |
|
|
17
|
+
|
|
18
|
+
## Workflow
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
Code change
|
|
22
|
+
│
|
|
23
|
+
launch slot
|
|
24
|
+
│
|
|
25
|
+
typecheck + unit-tests
|
|
26
|
+
│
|
|
27
|
+
sast ◄── semgrep scan over the worktree
|
|
28
|
+
│
|
|
29
|
+
pass/fail
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Full verify (`verify --full`) runs the scan after lint. Reports (JSON + SARIF)
|
|
33
|
+
land under `.har/artifacts/sast/`.
|
|
34
|
+
|
|
35
|
+
## Requirements
|
|
36
|
+
|
|
37
|
+
- `semgrep` CLI: `pipx install semgrep` (recommended), `pip install semgrep`,
|
|
38
|
+
or `brew install semgrep`
|
|
39
|
+
- Network access for registry rulesets (`auto`, `p/ci`, …) — or commit local
|
|
40
|
+
rules and set `HARNESS_SEMGREP_CONFIG` to their path for offline scans
|
|
41
|
+
|
|
42
|
+
## Compliance (Vanta)
|
|
43
|
+
|
|
44
|
+
The local stage is shift-left only — compliance platforms never see it. The CI
|
|
45
|
+
workflow's `semgrep ci` run (with a `SEMGREP_APP_TOKEN` secret) publishes to the
|
|
46
|
+
Semgrep AppSec Platform, which Vanta ingests natively as vulnerability-management
|
|
47
|
+
evidence. See `.har/stages/SEMGREP.md` for the full story.
|
|
48
|
+
|
|
49
|
+
## Quick start after install
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pipx install semgrep
|
|
53
|
+
./.har/launch.sh 1
|
|
54
|
+
./.har/stages/sast.sh 1
|
|
55
|
+
# reports: .har/artifacts/sast/semgrep.json (+ .sarif, scan.log)
|
|
56
|
+
./.har/verify.sh 1 --full
|
|
57
|
+
```
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "semgrep",
|
|
3
|
+
"stageId": "sast",
|
|
4
|
+
"verificationStages": [
|
|
5
|
+
"typecheck",
|
|
6
|
+
"unit-tests",
|
|
7
|
+
"lint",
|
|
8
|
+
"sast"
|
|
9
|
+
],
|
|
10
|
+
"stage": {
|
|
11
|
+
"id": "sast",
|
|
12
|
+
"kind": "test",
|
|
13
|
+
"description": "Static analysis (SAST) with Semgrep — scans the session worktree for security findings",
|
|
14
|
+
"script": "stages/sast.sh",
|
|
15
|
+
"requiresAgentId": true,
|
|
16
|
+
"artifacts": [
|
|
17
|
+
{
|
|
18
|
+
"path": ".har/artifacts/sast",
|
|
19
|
+
"kind": "directory",
|
|
20
|
+
"description": "Semgrep JSON + SARIF reports and scan log"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"path": ".har/artifacts/sast/semgrep.json",
|
|
24
|
+
"kind": "report"
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
{ "src": ".har/stages/sast.sh", "dest": ".har/stages/sast.sh", "executable": true },
|
|
30
|
+
{ "src": ".har/stages/SEMGREP.md", "dest": ".har/stages/SEMGREP.md" }
|
|
31
|
+
],
|
|
32
|
+
"optionalFiles": [
|
|
33
|
+
{ "src": ".github/workflows/semgrep.yml", "dest": ".github/workflows/semgrep.yml", "skipFlag": "skipCi" }
|
|
34
|
+
],
|
|
35
|
+
"nextSteps": [
|
|
36
|
+
"pipx install semgrep # or: pip install semgrep / brew install semgrep",
|
|
37
|
+
"./.har/launch.sh 1",
|
|
38
|
+
"./.har/stages/sast.sh 1",
|
|
39
|
+
"Open .har/artifacts/sast/ for the JSON and SARIF reports",
|
|
40
|
+
"Read .har/stages/SEMGREP.md to pin rulesets (HARNESS_SEMGREP_CONFIG) or add local rules",
|
|
41
|
+
"CI: set the SEMGREP_APP_TOKEN secret so semgrep ci reports to the Semgrep AppSec Platform (compliance evidence, e.g. Vanta)"
|
|
42
|
+
],
|
|
43
|
+
"docsPath": ".har/stages/SEMGREP.md"
|
|
44
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: Trivy scan
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
scan:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
security-events: write
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- name: Run Trivy (filesystem scan)
|
|
17
|
+
uses: aquasecurity/trivy-action@a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8 # v0.36.0
|
|
18
|
+
with:
|
|
19
|
+
scan-type: fs
|
|
20
|
+
scan-ref: .
|
|
21
|
+
scanners: vuln,misconfig
|
|
22
|
+
severity: HIGH,CRITICAL
|
|
23
|
+
format: sarif
|
|
24
|
+
output: trivy-results.sarif
|
|
25
|
+
exit-code: 0
|
|
26
|
+
- name: Upload SARIF to GitHub code scanning
|
|
27
|
+
if: always()
|
|
28
|
+
uses: github/codeql-action/upload-sarif@v3
|
|
29
|
+
with:
|
|
30
|
+
sarif_file: trivy-results.sarif
|
|
31
|
+
- name: Fail on HIGH/CRITICAL findings
|
|
32
|
+
uses: aquasecurity/trivy-action@a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8 # v0.36.0
|
|
33
|
+
with:
|
|
34
|
+
scan-type: fs
|
|
35
|
+
scan-ref: .
|
|
36
|
+
scanners: vuln,misconfig
|
|
37
|
+
severity: HIGH,CRITICAL
|
|
38
|
+
format: table
|
|
39
|
+
exit-code: 1
|
|
40
|
+
skip-setup-trivy: true
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Trivy Vulnerability & Misconfiguration Scanning
|
|
2
|
+
|
|
3
|
+
The `vuln-scan` stage runs [Trivy](https://trivy.dev) against the agent's
|
|
4
|
+
worktree: known CVEs in dependency lockfiles (`vuln` scanner) and IaC/config
|
|
5
|
+
misconfigurations in Terraform, Dockerfiles, Kubernetes manifests, Helm charts,
|
|
6
|
+
and CloudFormation (`misconfig` scanner). Full verify (`verify --full`) runs it
|
|
7
|
+
automatically.
|
|
8
|
+
|
|
9
|
+
Trivy absorbed **tfsec** — its Terraform checks now live in Trivy's `misconfig`
|
|
10
|
+
scanner, so this plugin covers the Terraform/IaC use case without a separate tool.
|
|
11
|
+
|
|
12
|
+
## What runs
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
trivy fs --scanners vuln,misconfig --severity HIGH,CRITICAL --exit-code 1 \
|
|
16
|
+
--format json --output .har/artifacts/vuln-scan/report.json <work-dir>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The stage **fails** when any finding at or above the severity threshold exists.
|
|
20
|
+
Findings below the threshold are not reported at all — raise or lower the bar
|
|
21
|
+
via configuration.
|
|
22
|
+
|
|
23
|
+
## Configuration
|
|
24
|
+
|
|
25
|
+
Set in `harness.env` (or export before running):
|
|
26
|
+
|
|
27
|
+
| Variable | Default | Purpose |
|
|
28
|
+
|----------|---------|---------|
|
|
29
|
+
| `HARNESS_TRIVY_SEVERITY` | `HIGH,CRITICAL` | Comma-separated severities that fail the stage (`UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL`) |
|
|
30
|
+
| `HARNESS_TRIVY_SCANNERS` | `vuln,misconfig` | Scanners to run (`vuln`, `misconfig`, `secret`, `license`) |
|
|
31
|
+
| `TRIVY_CACHE_DIR` | `~/.cache/trivy` | Vulnerability DB cache — kept **outside** the worktrees so every slot shares one DB download (~60MB, refreshed every ~6h) |
|
|
32
|
+
|
|
33
|
+
## Artifacts
|
|
34
|
+
|
|
35
|
+
| Path | Contents |
|
|
36
|
+
|------|----------|
|
|
37
|
+
| `.har/artifacts/vuln-scan/report.json` | Full Trivy JSON report (machine-readable) |
|
|
38
|
+
| `.har/artifacts/vuln-scan/summary.txt` | Human-readable table (`trivy convert`) |
|
|
39
|
+
|
|
40
|
+
Artifacts always land in the **main repo root** `.har/artifacts/`, not the worktree.
|
|
41
|
+
|
|
42
|
+
## Suppressing findings
|
|
43
|
+
|
|
44
|
+
Add IDs to `.trivyignore` at the repo root — one CVE / check ID per line, no
|
|
45
|
+
trailing comments on the ID line:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
# Accepted risk: dev-only dependency, fix tracked in TICKET-123 (expires when bumped)
|
|
49
|
+
CVE-2021-23337
|
|
50
|
+
# Terraform check that conflicts with our architecture
|
|
51
|
+
AVD-AWS-0086
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The stage passes the **worktree's** copy via `--ignorefile`, so a suppression
|
|
55
|
+
travels with the change batch that introduces it and gets reviewed in the same
|
|
56
|
+
PR. Prefer fixing (bump the dependency, correct the config) over ignoring.
|
|
57
|
+
|
|
58
|
+
## Adaptations per repo
|
|
59
|
+
|
|
60
|
+
- **Container images**: the default stage scans the filesystem only. If the repo
|
|
61
|
+
builds an image, add a second invocation after the build:
|
|
62
|
+
`trivy image --severity "$SEVERITY" --exit-code 1 <image:tag>`.
|
|
63
|
+
- **Monorepos**: scope the scan by replacing `"$WORK_DIR"` with a subdirectory,
|
|
64
|
+
or add `--skip-dirs 'vendor/**,examples/**'`.
|
|
65
|
+
- **Lockfile-only scanning**: Trivy reads lockfiles (`package-lock.json`,
|
|
66
|
+
`yarn.lock`, `poetry.lock`, `go.sum`, `Cargo.lock`, …). Repos without a
|
|
67
|
+
lockfile get no dependency findings — commit one for meaningful results.
|
|
68
|
+
- **Secrets**: this harness pairs well with a dedicated secrets stage
|
|
69
|
+
(gitleaks plugin, which scans git history, not just the checkout). Add
|
|
70
|
+
`secret` to `HARNESS_TRIVY_SCANNERS` only if you don't run one.
|
|
71
|
+
|
|
72
|
+
## CI and compliance platforms (Vanta, Drata, …)
|
|
73
|
+
|
|
74
|
+
The local stage is **pre-merge shift-left** — it keeps findings from ever
|
|
75
|
+
reaching your default branch. Compliance platforms never see local runs.
|
|
76
|
+
|
|
77
|
+
The optional workflow `.github/workflows/trivy.yml` runs the same scan in CI and
|
|
78
|
+
uploads SARIF to **GitHub code scanning** (the repo's Security tab). That
|
|
79
|
+
org-level layer is what compliance platforms like Vanta ingest as evidence (via
|
|
80
|
+
their GitHub integration). Keep both: local stage to catch early, CI workflow to
|
|
81
|
+
produce the evidence.
|
|
82
|
+
|
|
83
|
+
## Running
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
./.har/stages/vuln-scan.sh 1 # standalone
|
|
87
|
+
./.har/verify.sh 1 --full # as part of full verification
|
|
88
|
+
```
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Trivy vulnerability + misconfiguration scan for an agent slot.
|
|
3
|
+
# Scans the agent's worktree filesystem: dependency lockfiles (vuln) and
|
|
4
|
+
# IaC/config files — Terraform, Dockerfile, Kubernetes, CloudFormation (misconfig).
|
|
5
|
+
# Outputs JSON to stdout, human-readable progress to stderr.
|
|
6
|
+
#
|
|
7
|
+
# Usage: ./.har/stages/vuln-scan.sh <agent-id>
|
|
8
|
+
#
|
|
9
|
+
# Tune via harness.env or the environment:
|
|
10
|
+
# HARNESS_TRIVY_SEVERITY fail threshold (default: HIGH,CRITICAL)
|
|
11
|
+
# HARNESS_TRIVY_SCANNERS scanners to run (default: vuln,misconfig)
|
|
12
|
+
# TRIVY_CACHE_DIR vulnerability DB cache (default: ~/.cache/trivy —
|
|
13
|
+
# shared across slots so the DB downloads once)
|
|
14
|
+
#
|
|
15
|
+
# Prerequisite: ./.har/launch.sh <agent-id>
|
|
16
|
+
# See: ./.har/stages/TRIVY.md for the full adaptation guide.
|
|
17
|
+
set -euo pipefail
|
|
18
|
+
|
|
19
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
20
|
+
HARNESS_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
21
|
+
REPO_ROOT="$(cd "$HARNESS_DIR/.." && pwd)"
|
|
22
|
+
# agent-slot.sh expects SCRIPT_DIR to be .har/ (slot registry lives there)
|
|
23
|
+
SCRIPT_DIR="$HARNESS_DIR"
|
|
24
|
+
|
|
25
|
+
# shellcheck source=/dev/null
|
|
26
|
+
source "$HARNESS_DIR/harness.env"
|
|
27
|
+
# shellcheck source=/dev/null
|
|
28
|
+
source "$HARNESS_DIR/agent-slot.sh"
|
|
29
|
+
|
|
30
|
+
AGENT_ID="${1:?Usage: vuln-scan.sh <agent-id>}"
|
|
31
|
+
|
|
32
|
+
validate_agent_id "$AGENT_ID"
|
|
33
|
+
|
|
34
|
+
log() { echo "==> [vuln-scan agent-$AGENT_ID] $*" >&2; }
|
|
35
|
+
|
|
36
|
+
# ── Preflight ─────────────────────────────────────────────────────────────────
|
|
37
|
+
if ! command -v trivy >/dev/null 2>&1; then
|
|
38
|
+
echo "Error: trivy CLI not found." >&2
|
|
39
|
+
echo " Install: https://trivy.dev/latest/getting-started/installation/" >&2
|
|
40
|
+
echo " macOS: brew install trivy" >&2
|
|
41
|
+
echo " Linux: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b ~/.local/bin" >&2
|
|
42
|
+
echo " Then re-run: ./.har/stages/vuln-scan.sh ${AGENT_ID}" >&2
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# ── Resolve agent env ─────────────────────────────────────────────────────────
|
|
47
|
+
ENV_FILE="$(resolve_agent_env_file "$AGENT_ID" "$REPO_ROOT")" || {
|
|
48
|
+
echo "No .env.agent.${AGENT_ID} found." >&2
|
|
49
|
+
har_suggest_launch "$AGENT_ID" >&2
|
|
50
|
+
exit 1
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
set -a
|
|
54
|
+
# shellcheck source=/dev/null
|
|
55
|
+
source "$ENV_FILE"
|
|
56
|
+
set +a
|
|
57
|
+
|
|
58
|
+
WORK_DIR="$(resolve_agent_work_dir "$ENV_FILE")"
|
|
59
|
+
|
|
60
|
+
# ── Configuration ─────────────────────────────────────────────────────────────
|
|
61
|
+
SEVERITY="${HARNESS_TRIVY_SEVERITY:-HIGH,CRITICAL}"
|
|
62
|
+
SCANNERS="${HARNESS_TRIVY_SCANNERS:-vuln,misconfig}"
|
|
63
|
+
# One shared DB cache for every slot/worktree — first run downloads ~60MB,
|
|
64
|
+
# subsequent runs reuse it.
|
|
65
|
+
export TRIVY_CACHE_DIR="${TRIVY_CACHE_DIR:-$HOME/.cache/trivy}"
|
|
66
|
+
|
|
67
|
+
# ── Artifact directory ────────────────────────────────────────────────────────
|
|
68
|
+
ARTIFACT_DIR="$REPO_ROOT/.har/artifacts/vuln-scan"
|
|
69
|
+
mkdir -p "$ARTIFACT_DIR"
|
|
70
|
+
REPORT_JSON="$ARTIFACT_DIR/report.json"
|
|
71
|
+
|
|
72
|
+
# ── Scan ──────────────────────────────────────────────────────────────────────
|
|
73
|
+
# Use the WORKTREE's .trivyignore (not the main checkout's) so suppressions are
|
|
74
|
+
# reviewed with the code change that introduces them.
|
|
75
|
+
IGNOREFILE_ARGS=()
|
|
76
|
+
if [ -f "$WORK_DIR/.trivyignore" ]; then
|
|
77
|
+
IGNOREFILE_ARGS=(--ignorefile "$WORK_DIR/.trivyignore")
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
log "Scanning $WORK_DIR (scanners: $SCANNERS, fail on: $SEVERITY)..."
|
|
81
|
+
START=$(now_ms)
|
|
82
|
+
|
|
83
|
+
set +e
|
|
84
|
+
trivy fs \
|
|
85
|
+
--scanners "$SCANNERS" \
|
|
86
|
+
--severity "$SEVERITY" \
|
|
87
|
+
--exit-code 1 \
|
|
88
|
+
--format json \
|
|
89
|
+
--output "$REPORT_JSON" \
|
|
90
|
+
--no-progress \
|
|
91
|
+
${IGNOREFILE_ARGS[@]+"${IGNOREFILE_ARGS[@]}"} \
|
|
92
|
+
"$WORK_DIR" 2> >(sed 's/^/ /' >&2)
|
|
93
|
+
TRIVY_EXIT=$?
|
|
94
|
+
set -e
|
|
95
|
+
|
|
96
|
+
END=$(now_ms)
|
|
97
|
+
TOTAL_MS=$(( END - START ))
|
|
98
|
+
|
|
99
|
+
if [ ! -f "$REPORT_JSON" ]; then
|
|
100
|
+
echo "Error: trivy did not produce a report (exit $TRIVY_EXIT) — see stderr above." >&2
|
|
101
|
+
exit "$TRIVY_EXIT"
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
# Human-readable summary next to the JSON report (best effort)
|
|
105
|
+
trivy convert --format table "$REPORT_JSON" > "$ARTIFACT_DIR/summary.txt" 2>/dev/null || true
|
|
106
|
+
|
|
107
|
+
# ── Output ────────────────────────────────────────────────────────────────────
|
|
108
|
+
STATUS="pass"
|
|
109
|
+
if [ "$TRIVY_EXIT" -ne 0 ]; then
|
|
110
|
+
STATUS="fail"
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
node -e "
|
|
114
|
+
const fs = require('fs');
|
|
115
|
+
const report = JSON.parse(fs.readFileSync('$REPORT_JSON', 'utf8'));
|
|
116
|
+
const results = report.Results || [];
|
|
117
|
+
let vulnerabilities = 0;
|
|
118
|
+
let misconfigurations = 0;
|
|
119
|
+
let secrets = 0;
|
|
120
|
+
const findings = [];
|
|
121
|
+
for (const r of results) {
|
|
122
|
+
for (const v of r.Vulnerabilities || []) {
|
|
123
|
+
vulnerabilities++;
|
|
124
|
+
findings.push({ type: 'vuln', target: r.Target, id: v.VulnerabilityID, pkg: v.PkgName, installed: v.InstalledVersion, fixed: v.FixedVersion || null, severity: v.Severity, title: (v.Title || '').slice(0, 120) });
|
|
125
|
+
}
|
|
126
|
+
for (const m of r.Misconfigurations || []) {
|
|
127
|
+
misconfigurations++;
|
|
128
|
+
findings.push({ type: 'misconfig', target: r.Target, id: m.ID, severity: m.Severity, title: (m.Title || '').slice(0, 120) });
|
|
129
|
+
}
|
|
130
|
+
for (const s of r.Secrets || []) {
|
|
131
|
+
secrets++;
|
|
132
|
+
findings.push({ type: 'secret', target: r.Target, id: s.RuleID, severity: s.Severity, title: (s.Title || '').slice(0, 120) });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const out = {
|
|
136
|
+
status: '$STATUS',
|
|
137
|
+
stageId: 'vuln-scan',
|
|
138
|
+
kind: 'test',
|
|
139
|
+
agent_id: $AGENT_ID,
|
|
140
|
+
total_ms: $TOTAL_MS,
|
|
141
|
+
severity_threshold: '$SEVERITY',
|
|
142
|
+
scanners: '$SCANNERS',
|
|
143
|
+
counts: { vulnerabilities, misconfigurations, secrets },
|
|
144
|
+
findings: findings.slice(0, 50),
|
|
145
|
+
artifacts: [
|
|
146
|
+
{ path: '.har/artifacts/vuln-scan/report.json', kind: 'report' },
|
|
147
|
+
{ path: '.har/artifacts/vuln-scan/summary.txt', kind: 'report' }
|
|
148
|
+
]
|
|
149
|
+
};
|
|
150
|
+
process.stdout.write(JSON.stringify(out, null, 2) + '\n');
|
|
151
|
+
"
|
|
152
|
+
|
|
153
|
+
if [ "$STATUS" = "fail" ]; then
|
|
154
|
+
log "✗ Findings at or above $SEVERITY — see .har/artifacts/vuln-scan/summary.txt"
|
|
155
|
+
exit 1
|
|
156
|
+
fi
|
|
157
|
+
log "✓ No findings at or above $SEVERITY (${TOTAL_MS}ms)"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# .trivyignore — suppress specific Trivy findings (one ID per line, no trailing
|
|
2
|
+
# comments). Prefer fixing over ignoring. Document every entry with a comment
|
|
3
|
+
# line above it saying why and when it can go.
|
|
4
|
+
#
|
|
5
|
+
# Example:
|
|
6
|
+
# # dev-only dependency, fix tracked in TICKET-123
|
|
7
|
+
# CVE-2021-23337
|
|
8
|
+
# # S3 public access is intentional for the www bucket
|
|
9
|
+
# AVD-AWS-0086
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Trivy plugin
|
|
2
|
+
|
|
3
|
+
Installs a `vuln-scan` stage that runs [Trivy](https://trivy.dev) against the
|
|
4
|
+
agent's worktree:
|
|
5
|
+
|
|
6
|
+
- **Dependency vulnerabilities** — known CVEs in lockfiles (`package-lock.json`,
|
|
7
|
+
`yarn.lock`, `poetry.lock`, `go.sum`, `Cargo.lock`, …)
|
|
8
|
+
- **IaC misconfigurations** — Terraform, Dockerfile, Kubernetes, Helm,
|
|
9
|
+
CloudFormation (Trivy absorbed tfsec, so this covers the Terraform use case)
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
har env add-plugin trivy # --skip-ci to omit the GitHub workflow
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
What it adds:
|
|
16
|
+
|
|
17
|
+
- `.har/stages/vuln-scan.sh` — the stage, registered in `.har/stages.json` and
|
|
18
|
+
appended to `verificationStages` (runs on `verify --full`)
|
|
19
|
+
- `.har/stages/TRIVY.md` — adaptation guide (severity threshold, scanners,
|
|
20
|
+
container-image scanning, monorepo scoping)
|
|
21
|
+
- `.trivyignore` — suppression file scaffold
|
|
22
|
+
- `.github/workflows/trivy.yml` *(optional)* — same scan in CI with SARIF upload
|
|
23
|
+
to GitHub code scanning; that org-level layer is what compliance platforms
|
|
24
|
+
like Vanta ingest as evidence
|
|
25
|
+
|
|
26
|
+
External requirement: the `trivy` binary (`brew install trivy` or the
|
|
27
|
+
[install script](https://trivy.dev/latest/getting-started/installation/)).
|
|
28
|
+
The stage fails fast with an install hint when it is missing.
|
|
29
|
+
|
|
30
|
+
Fail threshold defaults to `HIGH,CRITICAL` — override with
|
|
31
|
+
`HARNESS_TRIVY_SEVERITY` in `.har/harness.env`. Reports land in
|
|
32
|
+
`.har/artifacts/vuln-scan/`.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "trivy",
|
|
3
|
+
"stageId": "vuln-scan",
|
|
4
|
+
"verificationStages": [
|
|
5
|
+
"typecheck",
|
|
6
|
+
"unit-tests",
|
|
7
|
+
"lint",
|
|
8
|
+
"vuln-scan"
|
|
9
|
+
],
|
|
10
|
+
"stage": {
|
|
11
|
+
"id": "vuln-scan",
|
|
12
|
+
"kind": "test",
|
|
13
|
+
"description": "Trivy vulnerability + IaC misconfiguration scan (dependencies, Terraform, Dockerfile, K8s)",
|
|
14
|
+
"script": "stages/vuln-scan.sh",
|
|
15
|
+
"requiresAgentId": true,
|
|
16
|
+
"artifacts": [
|
|
17
|
+
{
|
|
18
|
+
"path": ".har/artifacts/vuln-scan",
|
|
19
|
+
"kind": "directory",
|
|
20
|
+
"description": "Trivy JSON report and human-readable summary"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"path": ".har/artifacts/vuln-scan/report.json",
|
|
24
|
+
"kind": "report"
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
{ "src": ".har/stages/vuln-scan.sh", "dest": ".har/stages/vuln-scan.sh", "executable": true },
|
|
30
|
+
{ "src": ".har/stages/TRIVY.md", "dest": ".har/stages/TRIVY.md" },
|
|
31
|
+
{ "src": ".trivyignore", "dest": ".trivyignore" }
|
|
32
|
+
],
|
|
33
|
+
"optionalFiles": [
|
|
34
|
+
{ "src": ".github/workflows/trivy.yml", "dest": ".github/workflows/trivy.yml", "skipFlag": "skipCi" }
|
|
35
|
+
],
|
|
36
|
+
"nextSteps": [
|
|
37
|
+
"Install trivy: brew install trivy (or https://trivy.dev/latest/getting-started/installation/)",
|
|
38
|
+
"./.har/launch.sh 1",
|
|
39
|
+
"./.har/stages/vuln-scan.sh 1",
|
|
40
|
+
"Open .har/artifacts/vuln-scan/summary.txt for findings",
|
|
41
|
+
"Tune HARNESS_TRIVY_SEVERITY / HARNESS_TRIVY_SCANNERS in .har/harness.env — read .har/stages/TRIVY.md"
|
|
42
|
+
],
|
|
43
|
+
"docsPath": ".har/stages/TRIVY.md"
|
|
44
|
+
}
|