This file provides guidance to LLM-based coding agents when working with code in this repository.
IMPORTANT: Keep this document accurate. If you detect that any information in this file is outdated or incorrect while working on the codebase, update it immediately—even if documentation changes are not part of your current task. This document must never contain lies or stale information. Treat accuracy here as a continuous responsibility, not a one-time effort.
This is a GitHub Action for Sysdig vulnerability scanning. It performs container image and IaC (Infrastructure as Code) analysis using the Sysdig CLI Scanner and generates SARIF reports for GitHub Security integration.
# Build TypeScript to build/ directory
npm run build
# Bundle for distribution (build + ncc bundle to dist/)
npm run prepare
# Run linter
npm run lint
# Run all tests
npm test
# Run a single test file
npx jest tests/domain/services/filtering.test.ts
# Run tests matching a pattern
npx jest --testNamePattern="should filter"
# Full CI check (lint + prepare + test)
npm run allThe codebase follows Clean Architecture with three layers:
Pure business logic with no external dependencies:
- scanresult/: Core entities -
ScanResult(root aggregate),Package,Vulnerability,Policy,AcceptedRisk,Layer - services/:
filtering.ts(composable filter functions),sorting.ts(sort by severity)
Key patterns:
ScanResultuses Maps for deduplication (vulnerabilities keyed by CVE, policies/risks by ID)- Bidirectional relationships:
Package↔Vulnerability↔AcceptedRisk(both sides maintain references, usehas()checks to prevent infinite loops) - Value objects are immutable type-safe enums with factory methods (
Severity.fromString(),PackageType.fromString())
Severity scale (inverted - lower value = more severe):
Critical (0) > High (1) > Medium (2) > Low (3) > Negligible (4) > Unknown (5)
Use isMoreSevereThan() for comparisons (compares numeric values inversely).
Package.suggestedFixVersion(): Scores fix versions by count of vulnerabilities fixed at each severity level, prioritizing versions that fix the most critical issues.
- use-cases/RunScanUseCase.ts: Main orchestrator
- ports/: Interfaces (
IScanner,IReportPresenter,IInputProvider,ScanConfig) - errors/:
ChecksumVerificationError(always fails),ReportParsingError,ScanExecutionError
Error handling strategy:
ChecksumVerificationError: Always callscore.setFailed()regardless of config- Other errors: Respects
stopOnProcessingErrorflag (if true, fails; if false, logs only) - Exit codes: 0=pass, 1=policy failure
- github/:
GitHubActionsInputProvider,ActionInputs(parsing/validation),SarifReportPresenter,SummaryReportPresenter - sysdig/:
SysdigCliScanner(CLI flag composition),SysdigCliScannerDownloader(SHA256 verification),JsonScanResultV1ToScanResultAdapter(6-phase hydration)
index.ts wires everything together: creates input provider, downloader, scanner, and presenters, then invokes RunScanUseCase.
| Aspect | VM Mode (default) | IaC Mode |
|---|---|---|
| Target | Container images (imageTag) |
File paths (iacScanPath) |
| Reports | SARIF + GitHub Summary generated | No reports (scanner output only) |
| Filters | severityAtLeast, packageTypes, excludeAccepted |
minimumSeverity (passed to scanner) |
| CLI flag | --output=json-file=result.json |
--iac |
Two modes controlled by groupByPackage option:
- Group by vulnerability (default): Each SARIF rule = one CVE, multiple results per affected package
- Group by package: Each SARIF rule = one vulnerable package, security-severity = max CVSS of all its vulnerabilities
Severity to SARIF level mapping:
- Critical, High → "error"
- Medium → "warning"
- Low, Negligible → "note"
Filters are composable higher-order functions in src/domain/services/filtering.ts:
type PackageFilterOption = (pkgs: Package[]) => Package[];Applied sequentially: packageTypes → notPackageTypes → minSeverity → excludeAccepted
sysdigSecureTokenrequired unlessstandalone: trueimageTagrequired for VM modeiacScanPathcannot be empty for IaC modeseverityAtLeastmust be valid enum value or "any"
Sysdig CLI JSON → JsonScanResultV1ToScanResultAdapter (6-phase hydration) → ScanResult
↓
filterPackages()
↓
SarifReportPresenter / SummaryReportPresenter
Adapter hydration phases:
- Create ScanResult with metadata
- Add Layers
- Add AcceptedRisks
- Add Vulnerabilities (link to risks)
- Add Packages (link to layers, vulnerabilities, risks)
- Add Policies (with bundles and rules)
Tests mirror the src/ structure in tests/.
Fixtures (tests/fixtures/vm/):
postgres_13.json: Real scan (40 vulns, 145 packages, 25 layers)report-test-v1.json: Large fixture for integration testsdummy-vuln-app_latest_accepted_risk_in_image.json: Accepted risk scenarios
Key test files:
tests/domain/scanresult/Version.test.ts- Semantic versioning, pre-release handlingtests/domain/scanresult/Package.test.ts- Fix version scoring algorithmtests/infrastructure/sysdig/JsonScanResultV1ToScanResultAdapter.test.ts- Risk association (vuln-level vs package-level)tests/infrastructure/github/SummaryReportPresenter.test.ts- Filtering, sorting, HTML output (744 lines)
Testing patterns:
- Jest with
ts-jest, mocking viajest.Mocked<Interface> beforeEachwithjest.resetAllMocks()for isolation- Factory helpers for test data creation
- Supports semantic versioning with pre-release (
1.0.0-alpha < 1.0.0) - Strips
vprefix for comparison (v1.0.0equals1.0.0) - Build metadata ignored (
1.0.0+build1equals1.0.0+build2)
Policy.getEvaluationResult(): Failed if ANY bundle fails (short-circuit)PolicyBundle.getEvaluationResult(): Failed if ANY rule fails- Two rule types:
PolicyBundleRulePkgVuln(package vulnerabilities),PolicyBundleRuleImageConfig(image config)
- If
sha256sumprovided: Uses that value - If not provided: Auto-fetches from
{url}.sha256 - Verification uses Node.js
crypto.createHash('sha256')
| Workflow | Trigger | Purpose |
|---|---|---|
ci.yaml |
PR to master | Pre-commit checks (lint, build, test) |
ci-scan.yaml |
All PRs | Dogfooding - tests the action itself with 7 parallel jobs |
scan.yaml |
Manual (workflow_dispatch) |
On-demand testing |
release.yml |
Push to master (package.json change) | Automated release |
stale.yml |
Daily cron + manual | Cleanup stale issues/PRs |
The action tests itself on every PR with these scenarios:
- scan-from-registry: Basic scan with severity filter (expects failure - vuln image)
- filtered-scan-from-registry: Group-by-package mode
- scan-with-old-scanner-version: Backward compatibility (v1.18.0)
- standalone-scan-from-registry: Offline mode with cached DB (donor scan pattern)
- scan-with-multiple-policies: IaC mode with multiple policies
- scan-with-correct-checksum: Checksum validation success
- scan-with-incorrect-checksum: Checksum validation failure (expects failure)
Pattern: continue-on-error: true → validate outcome in follow-up step
- Bump version in
package.jsonand merge to master release.ymldetects version change viajqcomparison- Creates git tag (e.g.,
v6.3.3) - Generates changelog with
git-chglog - Creates GitHub release with changelog body
- Force-updates major tag (e.g.,
v6) foruses: sysdiglabs/scan-action@v6
# Install pre-commit hooks
pre-commit install
# Run manually
pre-commit run --all-filesHooks run in order:
trailing-whitespace,end-of-file-fixer,check-yaml(exclude dist/)actionlint- Validates GitHub Actions workflow syntaxnpm audit fix- Fix vulnerabilitiesnpm run lint- ESLintnpm run prepare- Build dist/npm run test- Jest tests
KUBELAB_SECURE_API_TOKEN: Sysdig Secure API token for CI scans
There's an overrides section forcing undici@^7.0.0 to fix CVE GHSA-g9mf-h72j-4rw9. This is a workaround because @actions/http-client (dependency of @actions/github) pins a vulnerable version of undici.
Action required: Remove the override once @actions/http-client releases a version with undici >= 6.23.0. Check periodically with:
npm ls undici
npm audit