diff --git a/.claude/skills/go-fmt/SKILL.md b/.claude/skills/go-fmt/SKILL.md new file mode 100644 index 0000000..71bd547 --- /dev/null +++ b/.claude/skills/go-fmt/SKILL.md @@ -0,0 +1,52 @@ +--- +name: go-fmt +description: > + Runs go fmt on all Go source files that were modified as part of the current + code change. Invoke this skill after completing any code changes to Go files + to ensure consistent formatting before committing. +--- + +# Go Fmt Skill + +## When to invoke + +Run this skill after any code change that creates or modifies one or more `.go` files. + +## Steps + +1. **Identify changed Go files** — run the following command to list every `.go` file that differs from the last commit: + + ```bash + git diff --name-only HEAD | grep '\.go$' + ``` + + If there are no staged/unstaged changes yet (e.g. files were just written), list all `.go` files that were touched during this session instead. + +2. **Run go fmt** — format all changed files in one call: + + ```bash + go fmt ./... + ``` + + This is preferred over per-file invocation because it also catches any indirectly affected files in the same packages. + +3. **Verify the result** — confirm that `go fmt` exited with code `0`. If it exited with a non-zero code, read the error output, fix the root cause (usually a syntax error in the modified file), and re-run `go fmt ./...`. + +4. **Check for reformatted files** — run: + + ```bash + git diff --name-only + ``` + + Any files listed here were reformatted by `go fmt`. This is expected and correct. + +5. **Report the outcome** — summarise what happened in one or two lines: + - If files were reformatted: list them and note that formatting was applied. + - If no files changed after `go fmt`: state "All changed Go files were already correctly formatted." + - If an error occurred: report the error message and the file it came from. + +## Notes + +- `go fmt` is a no-op on already-correctly-formatted code, so it is always safe to run. +- Do **not** skip this skill for test files (`*_test.go`) — they must be formatted too. +- The module root for this repository is `/home/runner/work/ScopeGuardian/ScopeGuardian` (or the directory where `go.mod` lives). Always run `go fmt` from that directory so the `./...` pattern covers all packages. diff --git a/.claude/skills/update-readme/SKILL.md b/.claude/skills/update-readme/SKILL.md new file mode 100644 index 0000000..6e9ead1 --- /dev/null +++ b/.claude/skills/update-readme/SKILL.md @@ -0,0 +1,50 @@ +--- +name: update-readme +description: > + Reviews recent code changes and updates README.md to keep it accurate. + Invoke this skill after completing code changes that touch CLI flags, + environment variables, configuration schema, scanner integrations, + security-gate logic, DefectDojo sync behaviour, Docker image, or build + requirements. +--- + +# Update README Skill + +## When to invoke + +Run this skill after any code change that touches one of the following areas: + +| Area | Key files / packages | +|------|----------------------| +| CLI flags | `parser/` | +| Environment variables | `environnement_variable/` | +| Configuration file schema | `loader/`, `config.toml` | +| Scanner integration | `features/scans/*/`, `engine/` | +| Security gate logic | `features/security-gate/` | +| DefectDojo sync behaviour | `features/sync/`, `connectors/defectdojo/` | +| Docker image / binary paths | `Dockerfile` | +| Go module / build requirements | `go.mod`, `main.go` | + +## Steps + +1. **Read the changed files** — use the Read tool on every file you modified to recall exactly what changed. + +2. **Check each README section** — open `README.md` and evaluate the sections below against your changes: + + | README section | What to verify | + |----------------|----------------| + | **Prerequisites** | Tool names, binary paths, and minimum Go version are accurate | + | **Quick Start** | Example commands still work with the current flag names and env vars | + | **CLI Usage** | Flag table is complete; names, types, and descriptions match `parser/` | + | **Configuration File (`config.toml`)** | Every documented key exists; new keys are documented with examples | + | **Environment Variables** | Table matches the vars loaded in `environnement_variable/` | + | **How Engagements Are Handled** | Engagement naming and end-date rules are correct | + | **How the Sync Feature Works** | Import vs reimport logic, flow description | + | **How the Security Gate Works** | Threshold evaluation, severity ordering, exit code behaviour | + | **Running with Docker** | Image name, tool paths, and `docker run` examples are current | + +3. **Edit README.md** — for each section that is now inaccurate or missing information, apply the minimum edit needed to make it accurate. Do not rewrite sections that are still correct. + +4. **Report what changed** — after editing, briefly summarise which README sections were updated and why (one line per section is enough). + +5. **No-op case** — if none of the trigger areas were touched, state explicitly: "README.md does not require updates for this change." diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..0144923 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,166 @@ +# ScopeGuardian – Context for AI Assistants + +## Project Overview + +**ScopeGuardian** is a Go CLI tool that orchestrates security scanners (KICS, Grype/Syft, OpenGrep) against a codebase and optionally synchronises findings with [DefectDojo](https://github.com/DefectDojo/django-DefectDojo). It can enforce a security gate that exits with `-1` to block CI/CD pipelines when configured severity thresholds are exceeded. + +## Repository Layout + +``` +ScopeGuardian/ +├── main.go # Entry point: parse → load → scan → display → gate +├── config.toml # Sample configuration file +├── go.mod / go.sum # Go module (module name: ScopeGuardian, go 1.24.4) +├── Dockerfile # Multi-stage image (bundles KICS, Grype, Syft, OpenGrep) +├── docker-compose.yml # Local DefectDojo stack (PostgreSQL + Redis + Nginx) +├── .env.example # Template for environment variables +├── connectors/ +│ └── defectdojo/ # DefectDojo API client (import/reimport, findings, engagements) +│ └── client/ # HTTP client wrapper +├── display/ # Banner, findings table, JSON/CSV/raw dump +├── domains/ +│ ├── interfaces/ # ScanServiceImpl interface +│ └── models/ # Finding model, FindingStatus, FilterFindingsByStatus +├── engine/ +│ ├── engine.go # Two-phase parallel runner (prerequisites → dependents) +│ ├── engine_test.go +│ └── const.go # Log message constants +├── environnement_variable/ # Loads SCAN_DIR, DD_URL, DD_ACCESS_TOKEN from env +├── exec/ # Shell command execution helpers +├── features/ +│ ├── scans/ +│ │ ├── kics/ # KICS IaC scanner integration +│ │ ├── grype/ # Grype SCA scanner integration +│ │ ├── opengrep/ # OpenGrep SAST scanner integration +│ │ └── syft/ # Syft SBOM generator (prerequisite for Grype) +│ ├── security-gate/ # Threshold evaluation logic +│ └── sync/ # DefectDojo engagement resolution and finding marking +├── loader/ # TOML config loader +├── logger/ # Structured logging (slog wrapper + null logger) +└── parser/ # CLI flag parser (--projectName, --branch, --sync, etc.) +``` + +## Common Commands + +```bash +# Build +go build -o ScopeGuardian . + +# Run all tests +go test ./... + +# Run tests with verbose output +go test -v ./... + +# Run a basic scan (no sync, no gate) +SCAN_DIR=/path/to/repos ./ScopeGuardian --projectName my-service --branch main ./config.toml + +# Run a scan and write findings to JSON +SCAN_DIR=/path/to/repos ./ScopeGuardian --projectName my-service --branch main -o /tmp/findings.json ./config.toml + +# Run a scan, sync to DefectDojo, enforce gate +SCAN_DIR=/path/to/repos DD_URL=http://localhost:8080 DD_ACCESS_TOKEN= \ + ./ScopeGuardian --projectName my-service --branch main --sync --threshold critical=1,high=5 ./config.toml + +# Build Docker image +docker build -t ScopeGuardian . +``` + +## Environment Variables + +| Variable | Required | Description | +|----------|----------|-------------| +| `SCAN_DIR` | always | Base directory; scan paths and result files are resolved relative to this | +| `DD_URL` | with `--sync` | Base URL of the DefectDojo instance (e.g. `http://localhost:8080`) | +| `DD_ACCESS_TOKEN` | with `--sync` | DefectDojo API v2 token | + +## Key CLI Flags + +| Flag | Description | +|------|-------------| +| `--projectName` | Project/product name; must match a DefectDojo Product name when using `--sync` | +| `--branch` | Branch being scanned | +| `--sync` | Upload results to DefectDojo and fetch back statuses | +| `--threshold` | `severity=count[,...]` — security gate thresholds | +| `--filter` | Comma-separated statuses to display/write (`ACTIVE`, `INACTIVE`, `DUPLICATE`). Default: `ACTIVE` | +| `-q` | Quiet mode (suppress logs) | +| `-o` | Output file path for findings | +| `--format` | Output format: `json` (default), `csv`, `raw` | + +## Architecture Notes + +### Engine (two-phase parallel scan) +1. **Prerequisites** — Syft SBOM generation runs first; failure is recorded. +2. **Dependent/independent scanners** — Grype, KICS, OpenGrep run concurrently. Grype is skipped if Syft failed. + +### Scanner interface (`domains/interfaces/ScanServiceImpl`) +Every scanner implements: +- `Start() (bool, error)` — runs the scanner binary, writes output file to `$SCAN_DIR/results/` +- `LoadFindings() ([]models.Finding, error)` — parses the output file into `Finding` structs +- `Sync(engagementId int, branch string, ddService) error` — uploads results to DefectDojo + +### Finding model (`domains/models/Finding`) +Fields include: `Title`, `Severity`, `Description`, `FilePath`, `Status` (`ACTIVE`/`INACTIVE`/`DUPLICATE`). + +### DefectDojo sync behaviour +- **First run** → `POST /api/v2/import-scan/` +- **Subsequent runs** → `POST /api/v2/reimport-scan/` (closes old findings, preserves triage decisions) +- Engagement name pattern: `-` +- Protected branches → 1-year engagement end date; others → 1-week end date + +### Security gate +- Counts findings **≥ configured severity** (CRITICAL > HIGH > MEDIUM > LOW > INFO). +- When `--sync` is active the gate uses DefectDojo's deduplicated ACTIVE findings, not raw local output. +- Exit code `-1` on failure. + +## Configuration File (`config.toml`) + +```toml +title = "Scope-guardian configuration file" +protected_branches = ["main", "master"] +path = "./my-service" # Relative to SCAN_DIR + +[kics] +platform = "Dockerfile" # KICS --type filter + +[grype] +ignore_states = "not-fixed,unknown,wont-fix" +transitive_libraries = false # true = resolve transitive Java deps (slow) + +[opengrep] +exclude = ["tests/**"] +exclude_rule = [] + +# [proxy] +# http_proxy = "http://proxy.company.com:3128" +# https_proxy = "http://proxy.company.com:3128" +# no_proxy = "localhost,127.0.0.1" +# ssl_cert_file = "/path/to/ca.pem" +``` + +## Code Conventions + +- **Module path**: `ScopeGuardian` (no domain prefix) +- **Logging**: use `logger.Info`, `logger.Error`, `logger.Err(err)` from the `logger` package — never `fmt.Println` or `log.*` +- **Log constants**: define log message strings as `const` in the package (see `engine/const.go`) +- **Tests**: use `github.com/stretchr/testify` and `github.com/golang/mock` +- **Error handling**: log and continue (scanner errors do not abort the whole run) +- **No global state**: scanners are instantiated via `GetService(config)` factory functions +- **Proxy forwarding**: pass proxy env vars to all scanner sub-processes; set both uppercase and lowercase variants + +## External Tool Binary Paths (inside Docker / expected locations) + +| Tool | Path | +|------|------| +| KICS | `/opt/kics/bin/kics` | +| OpenGrep | `/opt/opengrep/bin/opengrep` | +| Syft | `/opt/syft/bin/syft` | +| Grype | `/opt/grype/bin/grype` | + +## Testing + +- Unit tests live alongside source files (`*_test.go`). +- Run `go test ./...` from the repo root. +- Mocks are generated with `github.com/golang/mock`. +- New code must be covered by unit tests with significant coverage; aim for full branch and error-path coverage on all non-trivial logic. +- No integration test suite is bundled; integration testing requires a live DefectDojo instance.