Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:

permissions:
contents: write
id-token: write # Required for keyless cosign signing

jobs:
release:
Expand All @@ -30,3 +31,40 @@ jobs:
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Install cosign
uses: sigstore/cosign-installer@v4.0.0

- name: Sign release artifacts
run: |
# Sign all tar.gz files
for file in dist/*.tar.gz; do
if [ -f "$file" ]; then
echo "Signing $file"
cosign sign-blob --yes "$file" \
--bundle "${file}.sigstore.json"
fi
done

# Sign all zip files
for file in dist/*.zip; do
if [ -f "$file" ]; then
echo "Signing $file"
cosign sign-blob --yes "$file" \
--bundle "${file}.sigstore.json"
fi
done

# Sign checksums file
if [ -f "dist/checksums.txt" ]; then
echo "Signing checksums.txt"
cosign sign-blob --yes "dist/checksums.txt" \
--bundle "dist/checksums.txt.sigstore.json"
fi

- name: Upload signatures to release
uses: softprops/action-gh-release@v2
with:
files: dist/*.sigstore.json
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,10 @@ lint: ## Run linter
@which golangci-lint || go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
golangci-lint run

.PHONY: release
release: ## Create a release using GoReleaser
goreleaser release --clean

.PHONY: release-snapshot
release-snapshot: ## Create a snapshot release (no tagging required)
goreleaser build --snapshot --clean --single-target
127 changes: 121 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,35 @@ sudo mv wraith osv-scanner /usr/local/bin/

For Windows, download the `.zip` file from the releases page and extract both `wraith.exe` and `osv-scanner.exe` to a directory in your PATH.

### Verifying Release Signatures

All release artifacts are signed with [Sigstore cosign](https://github.com/sigstore/cosign) for supply chain security.

```bash
# Install cosign
brew install cosign # macOS
# or download from https://github.com/sigstore/cosign/releases

# Verify a release artifact
cosign verify-blob wraith_linux_amd64.tar.gz \
--bundle wraith_linux_amd64.tar.gz.sigstore.json \
--certificate-identity-regexp 'https://github.com/ghostsecurity/wraith/.github/workflows/release.yml' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com'
```

**macOS Security Warning:**

When running the binary on macOS, you may see a Gatekeeper warning. This is because the binary is not signed with an Apple Developer certificate. To bypass:

```bash
# Remove quarantine attribute
xattr -d com.apple.quarantine ./wraith ./osv-scanner

# Or right-click the binary in Finder and select "Open"
```

The binaries are safe to run - verify with cosign signatures above.

## CLI Usage

```bash
Expand All @@ -50,10 +79,74 @@ wraith scan --output report.md go.mod

> **Note:** Flags must come before the lockfile path.

### Offline Mode

Wraith can scan without network access using a locally cached vulnerability database.

```bash
# Download the vulnerability database for offline use
wraith download-db

# Scan using only the local database (no network requests)
wraith scan --offline go.mod

# Download fresh database and scan in one command
wraith scan --offline --download-db go.mod
```

The database is stored in:
- Linux: `~/.cache/osv-scanner/`
- macOS: `~/Library/Caches/osv-scanner/`
- Windows: `%LOCALAPPDATA%\osv-scanner\`

### License Scanning

Check dependencies for license compliance.

```bash
# Show license information for all dependencies
wraith scan --licenses go.mod

# Enforce a license allowlist (fails if dependencies use other licenses)
wraith scan --license-allowlist MIT,Apache-2.0,BSD-3-Clause go.mod
```

When using `--license-allowlist`, the scan will fail (exit code 1) if any dependency has a license not in the allowlist. Dependencies with undetectable licenses are reported as `UNKNOWN`.

### Custom Configuration

Use an [osv-scanner config file](https://google.github.io/osv-scanner/configuration/) for advanced options like ignoring specific vulnerabilities or packages.

Config files are automatically discovered in these locations (no flag needed):
- `osv-scanner.toml` in the current directory
- `.osv-scanner.toml` in the current directory
- Parent directories (walking up)

```bash
# Automatic discovery - just add osv-scanner.toml to your repo
wraith scan go.mod

# Or specify a config file explicitly
wraith scan --config /path/to/custom-config.toml go.mod
```

Example `osv-scanner.toml`:
```toml
[[IgnoredVulns]]
id = "GO-2024-1234"
reason = "False positive - not exploitable in our usage"

[[PackageOverrides]]
name = "stdlib"
ecosystem = "Go"
ignore = true
reason = "Go stdlib license (BSD-3-Clause) not detected by osv-scanner"
```

### Exit Codes

- `0`: No vulnerabilities found
- `1`: Vulnerabilities found or error occurred
- `0`: No vulnerabilities or license violations found
- `1`: Vulnerabilities found, license violations found, or error occurred

## Library Installation

Expand Down Expand Up @@ -113,26 +206,48 @@ func main() {
// Set custom timeout
scanner.SetTimeout(10 * time.Minute)

// Scan lockfile
result, err := scanner.ScanLockfile("poetry.lock")
// Scan with options
result, err := scanner.ScanLockfile("poetry.lock",
wraith.WithOffline(), // Use local DB only
wraith.WithLicenseAllowlist("MIT", "Apache-2.0"), // Check licenses
wraith.WithConfigFile("osv-scanner.toml"), // Custom config
)
if err != nil {
log.Fatalf("Scan failed: %v", err)
}

// Process results
// Process vulnerability results
for _, pkg := range result.GetPackagesWithVulnerabilities() {
fmt.Printf("Package: %s v%s (%s)\n",
pkg.Package.Name,
pkg.Package.Version,
pkg.Package.Ecosystem)

for _, vuln := range pkg.Vulnerabilities {
fmt.Printf(" 🚨 %s: %s\n", vuln.ID, vuln.Summary)
fmt.Printf(" - %s: %s\n", vuln.ID, vuln.Summary)
}
}

// Process license violations
for _, pkg := range result.GetPackagesWithLicenseViolations() {
fmt.Printf("License violation: %s v%s - %v\n",
pkg.Package.Name,
pkg.Package.Version,
pkg.LicenseViolations)
}
}
```

### Scan Options

| Option | Description |
|--------|-------------|
| `WithOffline()` | Scan using only the local vulnerability database |
| `WithOfflineDownload()` | Download/refresh the local database before scanning |
| `WithConfigFile(path)` | Use a custom osv-scanner config file |
| `WithLicenses()` | Enable license scanning (informational) |
| `WithLicenseAllowlist(licenses...)` | Enforce a license allowlist |

### Pipeline Integration

```go
Expand Down
Loading
Loading