446 lines
20 KiB
Markdown
446 lines
20 KiB
Markdown
# Extension Challenges
|
|
|
|
You've built the base project. Now make it yours by extending it with new features.
|
|
|
|
These challenges are ordered by difficulty. Start with the easier ones to build confidence, then tackle the harder ones when you want to dive deeper.
|
|
|
|
## Easy Challenges
|
|
|
|
### Challenge 1: Add a Blocked Packages Policy Rule
|
|
|
|
**What to build:** A new policy rule that fails when specific packages are present in the dependency tree, regardless of whether they have vulnerabilities.
|
|
|
|
**Why it's useful:** Some packages are known-risky even without CVEs. Packages with single maintainers, packages that have been abandoned, or packages your organization has decided to ban. The `event-stream` attack wouldn't have triggered a vulnerability scan, but a "blocked packages" list would have caught `flatmap-stream` immediately.
|
|
|
|
**What you'll learn:**
|
|
- Extending the policy YAML schema
|
|
- Adding a new evaluation path in the policy engine
|
|
- Working with the dependency graph to check package names
|
|
|
|
**Hints:**
|
|
- Add `blocked_packages: ["lodash", "moment"]` to the `Policy` struct in `internal/policy/rules.go`
|
|
- In `internal/policy/engine.go`, iterate over all packages in all graphs and check against the blocked list
|
|
- The check should match on package name, not PURL, so you can block `lodash` without specifying a version
|
|
|
|
**Test it works:**
|
|
Create a policy file with `blocked_packages: ["lodash"]` and run `bomber check testdata/node-project --policy your-policy.yaml`. It should fail because the node test fixture depends on lodash.
|
|
|
|
### Challenge 2: Add Markdown Report Output
|
|
|
|
**What to build:** A `--format markdown` option that outputs the vulnerability report as a GitHub-flavored Markdown table.
|
|
|
|
**Why it's useful:** CI/CD pipelines often post results as PR comments. A Markdown table renders cleanly in GitHub, GitLab, and Bitbucket PR discussions.
|
|
|
|
**What you'll learn:**
|
|
- Adding a new output format to the report layer
|
|
- Working with `io.Writer` for composable output
|
|
- Extending CLI flag handling
|
|
|
|
**Hints:**
|
|
- Create `internal/report/markdown.go` with functions matching the terminal report signatures
|
|
- The vulnerability table should have columns: Package, Vulnerability ID, Severity, CVSS Score, Fix Version
|
|
- Register the format in `internal/cli/scan.go`, `vuln.go`, and `check.go` alongside `terminal` and `json`
|
|
|
|
**Test it works:**
|
|
Run `bomber vuln testdata/go-project --format markdown` and verify the output renders correctly when pasted into a GitHub issue.
|
|
|
|
### Challenge 3: Add `--direct-only` Flag
|
|
|
|
**What to build:** A flag that filters scan and vulnerability results to only show direct dependencies, ignoring transitives.
|
|
|
|
**Why it's useful:** When triaging vulnerabilities, direct dependencies are actionable (you can bump the version in your manifest). Transitive vulnerabilities require waiting for your direct dependency to update, which is a different workflow.
|
|
|
|
**What you'll learn:**
|
|
- Adding persistent flags to Cobra commands
|
|
- Filtering `ScanResult` and `VulnReport` based on `Package.Direct`
|
|
|
|
**Hints:**
|
|
- Add the flag in `internal/cli/root.go` with `rootCmd.PersistentFlags().BoolVar`
|
|
- Filter in `queryVulns` before sending to the API: only include packages where `pkg.Direct == true`
|
|
- Also filter `ScanResult` for the scan summary counts
|
|
|
|
**Test it works:**
|
|
Compare output of `bomber vuln testdata/go-project` with and without `--direct-only`. The direct-only version should show fewer packages.
|
|
|
|
## Intermediate Challenges
|
|
|
|
### Challenge 4: Add Rust Ecosystem Support
|
|
|
|
**What to build:** A parser for Rust projects that reads `Cargo.toml` and `Cargo.lock`.
|
|
|
|
**Real world application:** Rust is growing fast in systems programming and security tooling. Cargo.lock has a well-defined format with explicit dependency sections.
|
|
|
|
**What you'll learn:**
|
|
- Implementing the `DependencyParser` interface for a new ecosystem
|
|
- TOML parsing for Cargo's format (different from Python's pyproject.toml)
|
|
- PURL construction for the `cargo` type
|
|
|
|
**Implementation approach:**
|
|
|
|
1. **Add `EcosystemRust`** to the `Ecosystem` enum in `pkg/types/types.go` with a `String()` case returning `"rust"`
|
|
|
|
2. **Create `internal/parser/cargo.go`:**
|
|
- `Detect()`: check for `Cargo.toml`
|
|
- `Parse()`: read `Cargo.toml` for project name/version and direct dependencies, read `Cargo.lock` for resolved versions and the full dependency tree
|
|
- PURLs follow the format `pkg:cargo/serde@1.0.200`
|
|
|
|
3. **Register** in `internal/parser/registry.go`
|
|
|
|
4. **Create test fixtures** in `testdata/rust-project/` with a `Cargo.toml` and `Cargo.lock`
|
|
|
|
**Hints:**
|
|
- Cargo.lock has `[[package]]` TOML arrays with `name`, `version`, `source`, and `checksum` fields
|
|
- Dependencies in Cargo.lock are listed as `dependencies = ["dep1", "dep2 0.3.0 (registry+...)"]`
|
|
- The tricky part is parsing the dependency format, which can include version and source constraints
|
|
|
|
**Test it works:**
|
|
Create a test Rust project fixture and run `bomber scan testdata/rust-project`. Verify the package count and ecosystem detection.
|
|
|
|
### Challenge 5: Add SARIF Output for CI Integration
|
|
|
|
**What to build:** A `--format sarif` option that outputs vulnerability findings in SARIF v2.1.0 format for integration with GitHub Code Scanning, Azure DevOps, and other CI platforms.
|
|
|
|
**Real world application:** GitHub Actions can ingest SARIF files and display findings inline on pull requests. This is how tools like CodeQL, Snyk, and Trivy surface results in the GitHub UI.
|
|
|
|
**What you'll learn:**
|
|
- SARIF specification structure (runs, results, rules, locations)
|
|
- Building complex JSON documents with nested structs
|
|
- CI/CD integration patterns
|
|
|
|
**Implementation approach:**
|
|
|
|
1. **Create `internal/report/sarif.go`** with SARIF v2.1.0 types:
|
|
- `sarifLog` with `$schema`, `version`, `runs`
|
|
- Each vulnerability becomes a `result` with a `ruleId`, `message`, and `level`
|
|
- Map CVSS severity to SARIF levels: CRITICAL/HIGH = "error", MEDIUM = "warning", LOW = "note"
|
|
|
|
2. **Map Bomber data to SARIF fields:**
|
|
- Rule ID = vulnerability ID (e.g., `CVE-2023-44487`)
|
|
- Rule shortDescription = vulnerability summary
|
|
- Result message = package PURL + fix version if available
|
|
- Level = severity mapping
|
|
|
|
3. **Add format handling** in the CLI commands
|
|
|
|
**Hints:**
|
|
- The SARIF schema URL is `https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json`
|
|
- GitHub Actions uses `github/codeql-action/upload-sarif@v3` to ingest SARIF files
|
|
- Look at the secrets-scanner project's `internal/reporter/sarif.go` for a working SARIF implementation in the same codebase
|
|
|
|
**Test it works:**
|
|
Run `bomber vuln testdata/go-project --format sarif | jq .` and verify it's valid SARIF. Upload it to GitHub Code Scanning in a test repo.
|
|
|
|
### Challenge 6: Add License Detection
|
|
|
|
**What to build:** Extract license information from dependencies and add it to the SBOM output.
|
|
|
|
**Real world application:** SBOMs aren't just for vulnerability tracking. License compliance is the other major use case. Organizations need to know if they're using GPL-licensed code in proprietary software.
|
|
|
|
**What you'll learn:**
|
|
- SPDX license expression syntax
|
|
- Extending the `Package` type with new fields
|
|
- Extracting license data from ecosystem-specific sources
|
|
|
|
**Implementation approach:**
|
|
|
|
1. **Add `License string` field** to `types.Package`
|
|
|
|
2. **Go parser:** Extract license from `go.sum` or by running `go list -m -json` which includes a `Dir` field you can scan for LICENSE files
|
|
|
|
3. **Node parser:** Read the `license` field from `package.json` entries in `pnpm-lock.yaml`
|
|
|
|
4. **Python parser:** Read the `license` field from package metadata (this may require querying PyPI or reading `dist-info`)
|
|
|
|
5. **Update SBOM generators:**
|
|
- SPDX: set `licenseConcluded` and `licenseDeclared` fields
|
|
- CycloneDX: add `licenses` array to components
|
|
|
|
**Hints:**
|
|
- Start with just the Node parser, which has license info readily available in `package.json`
|
|
- SPDX license identifiers are standardized: "MIT", "Apache-2.0", "GPL-3.0-only"
|
|
- For packages where license can't be determined, use "NOASSERTION" (SPDX) or omit the field (CycloneDX)
|
|
|
|
## Advanced Challenges
|
|
|
|
### Challenge 7: Add Reachability Analysis
|
|
|
|
**What to build:** Determine whether a vulnerable function is actually called by the project, not just imported as a dependency.
|
|
|
|
**Why this is hard:** A project can depend on `golang.org/x/net` without ever calling the vulnerable HTTP/2 functions. Knowing the dependency is present is useful; knowing whether the vulnerable code path is reachable is much more actionable.
|
|
|
|
**What you'll learn:**
|
|
- Static analysis with Go's `go/ast` and `go/types` packages
|
|
- Call graph construction
|
|
- False positive reduction in vulnerability scanning
|
|
|
|
**Architecture changes needed:**
|
|
|
|
```
|
|
┌───────────────────┐ ┌───────────────────┐
|
|
│ Vulnerability │ │ Call Graph │
|
|
│ Match (existing) │────▶│ Analysis (new) │
|
|
│ pkg + vuln ID │ │ symbols reachable? │
|
|
└───────────────────┘ └───────────┬────────┘
|
|
│
|
|
▼
|
|
┌───────────────────┐
|
|
│ Enriched Match │
|
|
│ + reachable: bool │
|
|
└───────────────────┘
|
|
```
|
|
|
|
**Implementation steps:**
|
|
|
|
1. **Research phase:** Study Go's `golang.org/x/tools/go/callgraph` package and the `vulncheck` approach used by `govulncheck`
|
|
|
|
2. **Design phase:** Decide whether to do import-level analysis (which packages are imported) or symbol-level analysis (which functions are called). Import-level is simpler but less precise.
|
|
|
|
3. **Implementation phase:**
|
|
- Parse Go source files with `go/ast`
|
|
- Build an import graph
|
|
- Cross-reference with vulnerability affected symbols
|
|
- Add `Reachable bool` to `VulnMatch`
|
|
|
|
4. **Testing phase:** Create test fixtures with both reachable and unreachable vulnerable imports
|
|
|
|
**Gotchas:**
|
|
- Only feasible for Go projects (Go's tooling supports this analysis). For Node and Python, import-level analysis is the practical ceiling.
|
|
- Reflection and dynamic dispatch defeat static analysis. You'll need to flag these cases as "unknown reachability" rather than "not reachable."
|
|
|
|
### Challenge 8: Build a Dependency Diff Tool
|
|
|
|
**What to build:** A `bomber diff` command that compares two SBOMs and shows what changed: added packages, removed packages, version changes, new vulnerabilities, resolved vulnerabilities.
|
|
|
|
**Why this is hard:** Diffing SBOMs requires normalizing identifiers across formats, handling renames and version bumps, and presenting changes in a way that's actionable.
|
|
|
|
**What you'll learn:**
|
|
- SBOM parsing (the reverse of generation)
|
|
- Set operations on dependency data
|
|
- Changelog presentation for security context
|
|
|
|
**Implementation steps:**
|
|
|
|
1. **Create `internal/diff/diff.go`** with types:
|
|
```
|
|
Added []Package
|
|
Removed []Package
|
|
Changed []PackageChange (old version → new version)
|
|
NewVulns []VulnMatch
|
|
ResolvedVulns []VulnMatch
|
|
```
|
|
|
|
2. **Add `bomber diff old.json new.json`** command
|
|
|
|
3. **Handle both SPDX and CycloneDX** input formats by detecting the format from the JSON structure
|
|
|
|
4. **Run vulnerability scans** on both the old and new package sets to compute the vulnerability delta
|
|
|
|
**Gotchas:**
|
|
- PURLs without versions (possible in fallback parsing) make diffing ambiguous
|
|
- A package can be "changed" in one ecosystem and "removed + added" in another if it moved between lockfiles
|
|
|
|
### Challenge 9: Add GitHub Actions Integration
|
|
|
|
**What to build:** A complete GitHub Actions workflow that runs Bomber on every PR, caches results between runs, posts a comment with the vulnerability summary, and blocks merging if the policy fails.
|
|
|
|
**Why this is hard:** CI integration involves environment detection, artifact caching, GitHub API interaction, and idempotent comment posting (update existing comment rather than creating duplicates).
|
|
|
|
**What you'll learn:**
|
|
- GitHub Actions workflow syntax and caching
|
|
- GitHub REST API for PR comments
|
|
- CI/CD security scanning patterns used in production
|
|
|
|
**Implementation steps:**
|
|
|
|
1. **Create `.github/workflows/sbom-check.yml`:**
|
|
- Trigger on `pull_request`
|
|
- Install Bomber (use the install script or `go install`)
|
|
- Run `bomber check . --policy policy.yaml --format json`
|
|
- Cache `~/.bomber/cache.db` between runs
|
|
|
|
2. **Create a comment script** that reads Bomber's JSON output and formats it as a Markdown PR comment using `gh api`
|
|
|
|
3. **Handle idempotent comments:** search for existing Bomber comments on the PR and update them instead of creating new ones
|
|
|
|
4. **Add status check integration:** use `bomber check` exit code to pass/fail the check
|
|
|
|
**Gotchas:**
|
|
- The cache path needs to be relative or configurable for CI caching
|
|
- PR comment permissions require `pull-requests: write` in the workflow
|
|
- Rate limits on the GitHub API need handling for monorepos with many PRs
|
|
|
|
## Expert Challenges
|
|
|
|
### Challenge 10: Build a Vulnerability Database Mirror
|
|
|
|
**What to build:** A local mirror of the OSV database that Bomber queries instead of the live API. This eliminates network dependency and enables air-gapped environments.
|
|
|
|
**Prerequisites:** You should have completed the cache challenge and understand SQLite schema design.
|
|
|
|
**What you'll learn:**
|
|
- Database synchronization patterns
|
|
- Bulk data ingestion and indexing
|
|
- Offline security scanning (required in air-gapped government/defense environments)
|
|
|
|
**Planning this feature:**
|
|
|
|
Before you code, think through:
|
|
- How do you handle initial sync vs incremental updates?
|
|
- What's the storage footprint? (OSV has ~200,000+ vulnerability records)
|
|
- How do you query the local database with the same PURL-based interface?
|
|
|
|
**Implementation phases:**
|
|
|
|
**Phase 1: Database schema**
|
|
- Design SQLite tables for vulnerabilities, affected packages, and version ranges
|
|
- Index by PURL prefix (ecosystem type) for fast lookups
|
|
|
|
**Phase 2: Sync engine**
|
|
- Use OSV's bulk export (Google Cloud Storage buckets) or the list endpoint
|
|
- Implement incremental sync based on last-modified timestamps
|
|
|
|
**Phase 3: Local query client**
|
|
- Implement `vuln.Client` interface backed by the local database
|
|
- Replace HTTP queries with SQL queries
|
|
- Handle version range matching locally
|
|
|
|
**Phase 4: CLI integration**
|
|
- Add `bomber mirror sync` command to trigger sync
|
|
- Add `--offline` flag to use only the local mirror
|
|
- Fall back to API if mirror is stale
|
|
|
|
**Success criteria:**
|
|
- [ ] Initial sync completes in under 10 minutes
|
|
- [ ] Incremental sync completes in under 30 seconds
|
|
- [ ] Query results match live API results for the test fixtures
|
|
- [ ] Works without network access after initial sync
|
|
- [ ] Storage is under 500MB for the full OSV database
|
|
|
|
## Performance Challenges
|
|
|
|
### Challenge: Handle 10,000-Dependency Monorepos
|
|
|
|
**The goal:** Make Bomber handle a monorepo with 10,000+ total dependencies without running out of memory or taking more than 60 seconds (excluding API calls).
|
|
|
|
**Current bottleneck:** The dependency graph stores all packages in memory. SBOM generation serializes the entire graph to JSON. For very large projects, both the graph and the JSON output can be large.
|
|
|
|
**Optimization approaches:**
|
|
|
|
**Approach 1: Streaming SBOM generation**
|
|
- Instead of building the full JSON document in memory, stream SPDX/CycloneDX output using `json.Encoder`
|
|
- Gain: Constant memory for SBOM generation regardless of graph size
|
|
- Tradeoff: Can't pretty-print with `json.MarshalIndent` (need custom formatting)
|
|
|
|
**Approach 2: Parallel parsing**
|
|
- Run ecosystem parsers concurrently with `errgroup`
|
|
- Gain: A monorepo with Go, Node, and Python projects scans 3x faster
|
|
- Tradeoff: Need to synchronize graph accumulation
|
|
|
|
**Benchmark it:**
|
|
```bash
|
|
time bomber scan ./large-monorepo
|
|
time bomber generate ./large-monorepo --sbom-format cyclonedx > /dev/null
|
|
```
|
|
|
|
Target metrics:
|
|
- Scan phase: < 5 seconds for 10,000 packages
|
|
- SBOM generation: < 10 seconds, < 200MB RSS
|
|
|
|
### Challenge: Reduce Binary Size
|
|
|
|
**The goal:** Get the Bomber binary under 5MB (current is larger due to the SQLite dependency).
|
|
|
|
**Optimization approaches:**
|
|
|
|
- Use `go build -ldflags="-s -w"` (already in the Justfile, strips debug info and symbol table)
|
|
- Consider `upx` compression for distribution
|
|
- Profile the binary with `go tool nm` to identify large dependencies
|
|
- Evaluate whether `modernc.org/sqlite` can be replaced with a lighter cache (e.g., bbolt) if SQLite features aren't needed
|
|
|
|
## Security Challenges
|
|
|
|
### Challenge: Add SBOM Signing
|
|
|
|
**What to implement:** Cryptographic signing of generated SBOMs so consumers can verify they haven't been tampered with.
|
|
|
|
**Threat model:** An attacker who can modify SBOMs in transit or at rest could hide vulnerabilities or add fake clean reports. Signing prevents this.
|
|
|
|
**Implementation:**
|
|
- Use `cosign` (Sigstore) or `notation` (Notary v2) for keyless signing
|
|
- Add `--sign` flag to the generate command
|
|
- Attach the signature as a separate `.sig` file or embed it in the SBOM metadata
|
|
- Add `bomber verify <sbom-file>` command to validate signatures
|
|
|
|
**Testing the security:**
|
|
- Modify a signed SBOM and verify that `bomber verify` detects the tampering
|
|
- Verify that signing works with both SPDX and CycloneDX output
|
|
|
|
### Challenge: Add VEX (Vulnerability Exploitability eXchange) Support
|
|
|
|
**What to implement:** VEX document generation that states whether vulnerabilities in an SBOM are actually exploitable in your specific context.
|
|
|
|
**Real world application:** A vulnerability in a dependency you import but never call its affected function is "not affected." VEX lets you formally state this, reducing noise for downstream consumers of your SBOM.
|
|
|
|
**Implementation:**
|
|
- Add `bomber vex ./project` command
|
|
- For each vulnerability found, prompt or accept a status: "not_affected", "affected", "fixed", "under_investigation"
|
|
- Output VEX in CycloneDX VEX or OpenVEX format
|
|
- Store VEX statements in a local file (`.bomber/vex.json`) so they persist across scans
|
|
|
|
## Real World Integration Challenges
|
|
|
|
### Integrate with GitHub Dependency Graph
|
|
|
|
**The goal:** Upload Bomber's dependency data to GitHub's Dependency Graph API so it appears in the repository's "Insights > Dependency graph" tab.
|
|
|
|
**What you'll need:**
|
|
- GitHub API token with `repo` scope
|
|
- Understanding of GitHub's Dependency Submission API
|
|
- PURL-to-GitHub ecosystem mapping
|
|
|
|
**Implementation plan:**
|
|
1. Generate a Dependency Snapshot from Bomber's scan results
|
|
2. POST to `https://api.github.com/repos/{owner}/{repo}/dependency-graph/snapshots`
|
|
3. Handle the response and report success/failure
|
|
|
|
**Watch out for:**
|
|
- GitHub's snapshot format has specific requirements for manifest file paths
|
|
- Rate limits on the Dependency Submission API
|
|
|
|
### Integrate with Dependency-Track
|
|
|
|
**The goal:** Upload Bomber-generated SBOMs to an OWASP Dependency-Track instance for continuous monitoring.
|
|
|
|
**What you'll need:**
|
|
- A running Dependency-Track instance (Docker: `docker run -p 8080:8080 dependencytrack/apiserver`)
|
|
- API key from Dependency-Track settings
|
|
|
|
**Implementation plan:**
|
|
1. Add `bomber upload --server <url> --project <name>` command
|
|
2. Generate CycloneDX SBOM (Dependency-Track's preferred format)
|
|
3. POST to Dependency-Track's BOM upload API
|
|
4. Report the project URL for the uploaded SBOM
|
|
|
|
## Challenge Completion
|
|
|
|
Track your progress:
|
|
|
|
- [ ] Easy: Blocked packages policy rule
|
|
- [ ] Easy: Markdown report output
|
|
- [ ] Easy: Direct-only flag
|
|
- [ ] Intermediate: Rust ecosystem support
|
|
- [ ] Intermediate: SARIF output
|
|
- [ ] Intermediate: License detection
|
|
- [ ] Advanced: Reachability analysis
|
|
- [ ] Advanced: Dependency diff tool
|
|
- [ ] Advanced: GitHub Actions integration
|
|
- [ ] Expert: Vulnerability database mirror
|
|
- [ ] Performance: 10,000-dependency monorepos
|
|
- [ ] Performance: Binary size reduction
|
|
- [ ] Security: SBOM signing
|
|
- [ ] Security: VEX support
|
|
- [ ] Integration: GitHub Dependency Graph
|
|
- [ ] Integration: Dependency-Track
|
|
|
|
Completed all of them? You've built a production-grade supply chain security tool. Consider contributing your extensions back to the project or building a standalone tool based on what you've learned.
|