{"id":44883905,"url":"https://github.com/1homsi/gorisk","last_synced_at":"2026-02-24T10:01:04.060Z","repository":{"id":339008777,"uuid":"1160067200","full_name":"1homsi/gorisk","owner":"1homsi","description":"Supply-chain risk intelligence that maps what your dependencies can do","archived":false,"fork":false,"pushed_at":"2026-02-23T12:10:09.000Z","size":3286,"stargazers_count":13,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-23T15:57:03.046Z","etag":null,"topics":["cli","cve","dependency-analysis","github-action","go","golang","security","static-analysis","supply-chain"],"latest_commit_sha":null,"homepage":"https://github.com/1homsi/gorisk","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/1homsi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-17T14:01:13.000Z","updated_at":"2026-02-23T12:07:18.000Z","dependencies_parsed_at":"2026-02-24T10:00:41.470Z","dependency_job_id":null,"html_url":"https://github.com/1homsi/gorisk","commit_stats":null,"previous_names":["1homsi/gorisk"],"tags_count":45,"template":false,"template_full_name":null,"purl":"pkg:github/1homsi/gorisk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/1homsi%2Fgorisk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/1homsi%2Fgorisk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/1homsi%2Fgorisk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/1homsi%2Fgorisk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/1homsi","download_url":"https://codeload.github.com/1homsi/gorisk/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/1homsi%2Fgorisk/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29779262,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-24T04:54:30.205Z","status":"ssl_error","status_checked_at":"2026-02-24T04:53:58.628Z","response_time":75,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","cve","dependency-analysis","github-action","go","golang","security","static-analysis","supply-chain"],"created_at":"2026-02-17T17:05:49.203Z","updated_at":"2026-02-24T10:01:04.009Z","avatar_url":"https://github.com/1homsi.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# gorisk\n\n\u003cimg src=\"assets/gorisk.png\" alt=\"gorisk\" width=\"480\"/\u003e\n\n**Behavioral supply-chain risk intelligence from code structure.**\nMaps what your dependencies *can do* — network access, exec, filesystem writes, unsafe pointers — not just what CVEs they carry.\n\n---\n\n## Why gorisk\n\n| Tool | CVEs | Capabilities | Evidence | Upgrade risk | Blast radius | Polyglot | Offline | Free |\n|------|------|-------------|---------|--------------|-------------|----------|---------|------|\n| govulncheck | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |\n| Snyk | ✅ | ❌ | ❌ | partial | ❌ | partial | ❌ | SaaS |\n| goda | ❌ | ❌ | ❌ | ❌ | partial | ❌ | ✅ | ✅ |\n| GoSurf | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |\n| **gorisk** | via OSV | **✅** | **✅** | **✅** | **✅** | **✅** | **✅** | **✅** |\n\nKey differentiators:\n\n- **Polyglot** — pluggable `Analyzer` interface; ships with Go and Node.js today; Python, Rust, Java, and Ruby on the roadmap.\n- **Capability detection** — detect which packages can read files, make network calls, spawn processes, or use `unsafe`/`eval`. Know *what your dependencies can do* before they're in production.\n- **Evidence + confidence** — every capability detection is backed by file path, line number, match context, and a confidence score (import = 90%, call site = 60%, install script = 85%).\n- **Capability diff** — compare two versions of a dependency and detect capability escalation. If `v1.2.3 → v1.3.0` quietly added `exec` or `network`, gorisk flags it as a supply chain risk signal.\n- **Deterministic output** — all output is sorted; every scan produces a short SHA-256 graph checksum so CI can detect silent graph changes between runs.\n- **CVE listing** — full list of OSV vulnerability IDs per module, not just a count.\n- **Blast radius** — simulate removing a module and see exactly which packages and binaries break, plus LOC impact.\n- **Upgrade risk** — diff exported symbols between versions to detect breaking API changes before you upgrade.\n- **Health scoring** — combines commit activity, release cadence, archived status, and CVE count into a single score (parallel, 10 workers).\n- **Reachability** — prove a capability is reachable from `main` via callgraph (Go) or import graph (Node.js). Supports `--entry` to target a specific binary.\n- **History + trend** — snapshot risk over time, diff between snapshots, view score sparklines per module.\n- **CI-native** — SARIF output compatible with GitHub Code Scanning. Exit codes for policy gating. `--timings` flag for build profiling.\n\n---\n\n## Install\n\n```bash\ngo install github.com/1homsi/gorisk/cmd/gorisk@latest\n```\n\n---\n\n## Language support\n\ngorisk auto-detects the language from the project directory. Use `--lang` to override.\n\n```bash\ngorisk scan              # auto-detect\ngorisk scan --lang go    # force Go\ngorisk scan --lang node  # force Node.js\n```\n\nWhen both `go.mod` and `package.json` are present (monorepo), both analyzers run and their dependency graphs are merged.\n\n### Supported languages\n\n| Language | `--lang` | Status | Detection signal | Lockfile / manifest |\n|----------|----------|--------|-----------------|---------------------|\n| **Go** | `go` | ✅ stable | `go.mod` | `go.mod` + `go list` |\n| **Node.js** | `node` | ✅ stable | `package.json` | `package-lock.json` v1/v2/v3, `yarn.lock`, `pnpm-lock.yaml`; npm/yarn/pnpm workspaces (monorepos) |\n| Python | `python` | 🗓 planned | `requirements.txt` / `pyproject.toml` | `poetry.lock`, `Pipfile.lock`, `uv.lock` |\n| Rust | `rust` | 🗓 planned | `Cargo.toml` | `Cargo.lock` |\n| Java | `java` | 🗓 planned | `pom.xml` / `build.gradle` | Maven, Gradle lock files |\n| Ruby | `ruby` | 🗓 planned | `Gemfile` | `Gemfile.lock` |\n\nWant to add a language? The `Analyzer` interface is a single `Load(dir string) (*graph.DependencyGraph, error)` method — see [Contributing](#contributing).\n\n---\n\n## Capability taxonomy\n\nAll languages map to the same 9 capabilities. Risk level is derived from the total weight: **LOW** \u003c 10, **MEDIUM** ≥ 10, **HIGH** ≥ 30.\n\n| Capability | Weight | Meaning |\n|-----------|--------|---------|\n| `fs:read` | 5 | Reads from the filesystem |\n| `fs:write` | 10 | Writes to or deletes files |\n| `network` | 15 | Makes outbound network connections |\n| `exec` | 20 | Spawns subprocesses or shell commands |\n| `env` | 5 | Reads environment variables |\n| `crypto` | 5 | Uses cryptographic primitives |\n| `reflect` | 5 | Uses runtime reflection |\n| `unsafe` | 25 | Bypasses memory/type safety (`unsafe`, `eval`, `vm`) |\n| `plugin` | 20 | Loads or executes external code at runtime |\n\n### Capability detection per language\n\n#### Go\n\nDetects capabilities via static AST analysis of `.go` source files. Every detection records the source file, line number, the matched import path or call pattern, whether it was detected as an `import` or `callSite`, and a confidence score.\n\n| Import / call | Capabilities | Confidence |\n|--------------|--------------|------------|\n| `os`, `io/fs` | `fs:read`, `fs:write` | 90% (import) |\n| `net`, `net/http` | `network` | 90% (import) |\n| `os/exec` | `exec` | 90% (import) |\n| `os.Getenv` | `env` | 90% (import) |\n| `unsafe` | `unsafe` | 90% (import) |\n| `crypto/*` | `crypto` | 90% (import) |\n| `reflect` | `reflect` | 90% (import) |\n| `plugin` | `plugin` | 90% (import) |\n| `exec.Command(` | `exec` | 60% (callSite) |\n| `http.Get(`, `http.Post(` | `network` | 60% (callSite) |\n\n#### Node.js\n\nScans `.js`, `.ts`, `.tsx`, `.mjs`, `.cjs` files for `require()`, ESM `import`, and dynamic `import()` patterns. Also scans `package.json` install scripts (`preinstall`, `install`, `postinstall`) for shell invocations.\n\n| Import / call | Capabilities | Confidence |\n|--------------|--------------|------------|\n| `fs`, `node:fs`, `fs/promises` | `fs:read`, `fs:write` | 90% (import) |\n| `http`, `https`, `net`, `tls` | `network` | 90% (import) |\n| `child_process`, `worker_threads`, `cluster` | `exec` | 90% (import) |\n| `os`, `process` | `env` | 90% (import) |\n| `crypto` | `crypto` | 90% (import) |\n| `vm` | `unsafe` | 90% (import) |\n| `module`, dynamic `import()` | `plugin` | 90% (import) |\n| `eval(`, `new Function(` | `unsafe` | 60% (callSite) |\n| `exec(`, `spawn(`, `fork(` | `exec` | 60% (callSite) |\n| `fetch(`, `axios.`, `got(` | `network` | 60% (callSite) |\n| `readFile`, `writeFile`, `unlink(` | `fs:read` / `fs:write` | 60% (callSite) |\n| `process.env` | `env` | 60% (callSite) |\n| `preinstall`/`postinstall` with `curl`/`wget`/`bash` | `exec` + `network` | 85% (installScript) |\n\n---\n\n## Commands\n\n### `gorisk scan`\n\nFull scan: capabilities + health scoring + CVE listing + CI gate. Prints a **graph checksum** for reproducibility.\n\n```bash\n# Basic\ngorisk scan\n\n# Force language\ngorisk scan --lang go\ngorisk scan --lang node\n\n# Output formats\ngorisk scan --json\ngorisk scan --sarif \u003e results.sarif\n\n# CI failure threshold\ngorisk scan --fail-on medium      # fail if any MEDIUM+ risk package\ngorisk scan --fail-on low         # strictest: fail on any capability\n\n# Policy file (see Policy section below)\ngorisk scan --policy .gorisk-policy.json\n\n# Performance instrumentation\ngorisk scan --timings\n\n# Combination\ngorisk scan --policy policy.json --fail-on high --json\n```\n\n**Output (text):**\n\n```\ngraph checksum: a3f2b1c9d5e78f01\n\n=== Capability Report ===\n\nPACKAGE                  MODULE                   CAPABILITIES        SCORE  RISK\n─────────────────────────────────────────────────────────────────────────────────\ngolang.org/x/net/http2   golang.org/x/net         network               15  MEDIUM\nos/exec                  stdlib                   exec                  20  HIGH\n\n=== Health Report ===\n\nMODULE            VERSION       SCORE  CVEs  STATUS\n─────────────────────────────────────────────────\ngolang.org/x/net  v0.25.0          85     0  OK\n\n✓ PASSED\n```\n\n**`--timings` output (appended after normal output):**\n\n```\n=== Timings ===\ngraph load                1.23s\ncapability detect         0.08s\nhealth scoring            4.51s  (24 modules, 10 workers)\n  github API              3.92s  (48 calls)\n  osv API                 0.59s  (24 calls)\noutput formatting         0.01s\n────────────────────────────────────────\ntotal                     5.83s\n```\n\n**`--json` adds:**\n- `\"graph_checksum\"` — short SHA-256 of the dependency graph for diffing between CI runs\n\n```bash\ngorisk scan --json | jq .graph_checksum\n```\n\n**`--sarif`** produces SARIF 2.1.0 compatible with GitHub Code Scanning (rules GORISK001 = high-risk capability, GORISK002 = low health score).\n\n**Exit codes:** 0 = passed, 1 = policy failure, 2 = error.\n\n---\n\n### `gorisk explain`\n\nShow the *evidence* behind each capability detection — the exact file, line number, matched pattern, detection method, and confidence score.\n\n```bash\n# Show all capability evidence\ngorisk explain\n\n# Filter to a specific capability\ngorisk explain --cap exec\ngorisk explain --cap network\ngorisk explain --cap unsafe\n\n# Language-specific\ngorisk explain --lang node\n\n# JSON output (structured evidence for tooling)\ngorisk explain --json\ngorisk explain --cap exec --json\n```\n\n**Text output:**\n\n```\n=== Capability Evidence ===\n\nexec\n  github.com/foo/bar\n    vendor/bar/run.go:42     exec.Command      [callSite  60%]\n    vendor/bar/run.go:88     import \"os/exec\"  [import    90%]\n\nnetwork\n  golang.org/x/net\n    net/http.go:12           import \"net/http\"  [import   90%]\n```\n\n**`--json` output:** array of objects, one per `(package, capability)` pair:\n\n```json\n[\n  {\n    \"package\": \"github.com/foo/bar\",\n    \"module\": \"github.com/foo/bar\",\n    \"capability\": \"exec\",\n    \"evidence\": [\n      {\n        \"file\": \"/abs/path/run.go\",\n        \"line\": 42,\n        \"context\": \"exec.Command\",\n        \"via\": \"callSite\",\n        \"confidence\": 0.6\n      }\n    ]\n  }\n]\n```\n\n**`via` values:**\n- `import` — the capability was detected from an import statement (confidence: 0.90)\n- `callSite` — detected from a function call pattern (confidence: 0.60)\n- `installScript` — detected in `package.json` install scripts (confidence: 0.85)\n\n---\n\n### `gorisk capabilities`\n\nList all packages and their detected capabilities with risk scores.\n\n```bash\ngorisk capabilities\ngorisk capabilities --lang node\ngorisk capabilities --lang go\n\n# Filter by minimum risk level\ngorisk capabilities --min-risk low       # show everything (default)\ngorisk capabilities --min-risk medium    # MEDIUM and HIGH only\ngorisk capabilities --min-risk high      # HIGH only\n\n# JSON output\ngorisk capabilities --json\n```\n\n**Text output:**\n\n```\n=== Capability Report ===\n\nPACKAGE                          MODULE                           CAPABILITIES               SCORE  RISK\n─────────────────────────────────────────────────────────────────────────────────────────────────────────\ngolang.org/x/net/http2           golang.org/x/net                 network                      15  MEDIUM\n```\n\n**Exit code:** 1 if any HIGH risk package was found (useful for `set -e` pipelines).\n\n---\n\n### `gorisk reachability`\n\nProves whether risky capabilities are **actually reachable** from your code — not just present in a transitive dependency.\n\n- **Go**: uses SSA callgraph analysis (Rapid Type Analysis) starting from all `main()` and `init()` functions.\n- **Node.js**: traces `require`/`import`/`import()` paths from your project source files through the full dependency graph.\n\n```bash\n# Analyze all entrypoints\ngorisk reachability\ngorisk reachability --lang node\n\n# Filter by minimum risk level\ngorisk reachability --min-risk high     # only show HIGH risk packages\ngorisk reachability --min-risk medium\n\n# Target a specific binary (Go)\ngorisk reachability --entry cmd/server/main.go\ngorisk reachability --entry cmd/worker/main.go\n\n# Target a specific entrypoint (Node.js)\ngorisk reachability --entry src/app.ts\ngorisk reachability --entry src/worker.js\n\n# Combine flags\ngorisk reachability --entry cmd/server/main.go --min-risk high\ngorisk reachability --lang node --entry src/app.ts --json\n\n# JSON output\ngorisk reachability --json\n```\n\n**Text output:**\n\n```\ngolang.org/x/net/http2                              HIGH    REACHABLE\n  caps: network\nos/exec                                             HIGH    unreachable\n  caps: exec\n```\n\n- **REACHABLE** (coloured by risk) — the capability is exercised from your `main()` or entry file.\n- **unreachable** (grey) — the package is in the graph but not called from your code.\n\n**`--entry` use case:** In a monorepo with multiple binaries (`cmd/api`, `cmd/worker`, `cmd/cron`), each binary has a different reachable set. Targeting `cmd/api/main.go` shows only the capabilities reachable from the API service, helping you scope risk per binary.\n\n**`--json` output:**\n\n```json\n[\n  {\n    \"package\": \"golang.org/x/net/http2\",\n    \"reachable\": true,\n    \"risk\": \"HIGH\",\n    \"score\": 15,\n    \"capabilities\": [\"network\"]\n  }\n]\n```\n\n---\n\n### `gorisk graph`\n\nCompute transitive risk scores across the full dependency tree. Uses depth-weighted scoring: `effective = direct + transitive/2` (capped at 100).\n\n```bash\ngorisk graph\ngorisk graph --lang node\n\n# Filter by minimum risk level\ngorisk graph --min-risk medium\ngorisk graph --min-risk high\n\n# JSON output\ngorisk graph --json\n```\n\n**Output columns:** Module | Direct score | Transitive score | Effective score | Depth | Risk level\n\n---\n\n### `gorisk diff`\n\nCompare capabilities between two versions of a dependency. Detects **supply chain risk from capability escalation** — if an update quietly added `exec` or `network`, this catches it.\n\n```bash\ngorisk diff golang.org/x/net@v0.20.0 golang.org/x/net@v0.25.0\ngorisk diff --lang node lodash@4.17.20 lodash@4.17.21\ngorisk diff --json golang.org/x/net@v0.20.0 golang.org/x/net@v0.25.0\n```\n\n**Output:** per-package diff showing capabilities added (`+`) and removed (`-`).\n\n**Exit codes:** 0 = no escalation, 1 = escalation detected (exec/network/unsafe/plugin added).\n\n---\n\n### `gorisk upgrade`\n\nCheck for breaking API changes before upgrading a dependency. Diffs exported function signatures between the current and target version.\n\n```bash\ngorisk upgrade golang.org/x/tools@v0.29.0\ngorisk upgrade --lang node express@5.0.0\ngorisk upgrade --json golang.org/x/tools@v0.29.0\n```\n\n**Output:** list of breaking changes (removed symbols, signature changes) and new transitive dependencies introduced by the upgrade.\n\n---\n\n### `gorisk impact`\n\nSimulate removing a module and compute its **blast radius** — how many packages and binaries depend on it, and how many lines of code are transitively affected.\n\n```bash\ngorisk impact golang.org/x/tools\ngorisk impact golang.org/x/tools@v0.29.0   # specific version\ngorisk impact --json golang.org/x/tools\ngorisk impact --lang node lodash\n```\n\n**Output:**\n\n```\n=== Blast Radius Report ===\n\nModule:            golang.org/x/tools\nAffected Packages: 42\nAffected Binaries: 3\nLOC Touched:       18200\nMax Graph Depth:   5\n\nAffected Binaries:\n  cmd/gorisk/main.go\n  cmd/scanner/main.go\n  cmd/indexer/main.go\n```\n\n---\n\n### `gorisk sbom`\n\nExport a **CycloneDX 1.4 SBOM** with gorisk-specific extensions: capabilities per component, health score, and risk level.\n\n```bash\ngorisk sbom \u003e sbom.json\ngorisk sbom --lang node \u003e sbom.json\ngorisk sbom --format cyclonedx \u003e sbom.json\n```\n\nIntegrates with enterprise security platforms (Dependency-Track, FOSSA, etc.).\n\n---\n\n### `gorisk licenses`\n\nDetect license risk across all dependencies via GitHub API. Flags copyleft and unknown licenses.\n\n```bash\ngorisk licenses\ngorisk licenses --lang node\ngorisk licenses --json\n\n# Fail pipeline if risky licenses found\ngorisk licenses --fail-on-risky\n```\n\n**Risky licenses** (exit 1 with `--fail-on-risky`): GPL-2.0, GPL-3.0, AGPL-3.0, LGPL-2.1, LGPL-3.0, and `unknown`.\n\n---\n\n### `gorisk viz`\n\nGenerate an **interactive dependency risk graph** as a single self-contained HTML file. No server required — works offline and is shareable by email or as a PR comment attachment.\n\n```bash\ngorisk viz \u003e graph.html\ngorisk viz --min-risk medium \u003e graph.html\ngorisk viz --lang node \u003e graph.html\nopen graph.html\n```\n\n**Graph features:**\n- Nodes coloured by risk — 🔴 HIGH ≥ 30 · 🟡 MEDIUM ≥ 10 · 🟢 LOW \u003c 10\n- Node size scales with risk score\n- Hover a node to see its capabilities, score, file count, and import counts, with its edges highlighted\n- **Click a node** to enter focus mode — neighbours animate into a ring around it, everything else dims; click again or empty space to exit\n- **Blast radius mode** — highlights everything that depends on a clicked node (reverse reachability BFS)\n- **Path finder mode** — click source then target to highlight the shortest dependency path\n- **Module cluster hulls** — convex hulls group packages by module for visual organisation\n- **Capability filter** — chip buttons to show only packages with specific capabilities (exec, network, fs:write, …)\n- **Dark mode** — toggle in the settings panel (⚙)\n- Filter by risk level using the chip buttons in the header\n- Toggle edge visibility; edges shown faintly by default (hidden for very large graphs)\n- Search packages by name or module\n- Scroll to zoom, drag to pan, drag a node to pin it, double-click to unpin\n- Reset button (⊙) zooms to fit all nodes\n\nLarge graphs (\u003e 300 packages) use a phyllotaxis initial layout and freeze physics after settling to prevent jitter.\n\n---\n\n### `gorisk pr`\n\nDetects dependency changes between two git refs and reports new capabilities, capability escalation, and removed modules. Designed for **pull request checks**.\n\n```bash\ngorisk pr                                # diffs origin/main...HEAD\ngorisk pr --lang node\ngorisk pr --base origin/main --head HEAD\ngorisk pr --json\n```\n\n**Exit code:** 1 if a new HIGH risk dependency was introduced (ideal as a CI gate on PRs).\n\n---\n\n### `gorisk history`\n\nTrack dependency risk over time. Snapshots are stored in `.gorisk-history.json` (add to `.gitignore`). Up to 100 snapshots are retained.\n\n#### `gorisk history record`\n\nSnapshot the current risk state.\n\n```bash\ngorisk history record\ngorisk history record --lang node\ngorisk history record --lang go\n```\n\nCaptures: timestamp, git commit hash, all modules with risk level, effective score, and capabilities.\n\n#### `gorisk history show`\n\nList all recorded snapshots with a **trend column** showing how HIGH-risk module count changed from the previous snapshot.\n\n```bash\ngorisk history show\ngorisk history show --json\n```\n\n**Text output:**\n\n```\n#     TIMESTAMP                  COMMIT        MODULES   HIGH  MEDIUM    LOW  TREND\n──────────────────────────────────────────────────────────────────────────────────────\n1     2026-01-01T10:00:00Z       a1b2c3d           12      2       4      6  —\n2     2026-01-15T14:22:11Z       f4e5d6c           13      3       5      5  ↑ +1H\n3     2026-02-01T09:45:30Z       9a8b7c6           13      2       5      6  ↓ -1H\n```\n\n**TREND column:**\n- `—` — first snapshot or no HIGH-risk change from previous\n- `↑ +NH` (red) — HIGH-risk module count increased by N\n- `↓ -NH` (green) — HIGH-risk module count decreased by N\n- `→` (grey) — same HIGH count, no change\n\n#### `gorisk history diff`\n\nDiff two snapshots to see what changed between them.\n\n```bash\ngorisk history diff              # diff the last two snapshots\ngorisk history diff N            # diff snapshot N vs the latest\ngorisk history diff N M          # diff snapshot N vs snapshot M\ngorisk history diff --json       # JSON output\n```\n\n**Text output:**\n\n```\ndrift  2026-01-01T10:00:00Z → 2026-02-01T09:45:30Z\n\n  +  github.com/some/new-dep                             MEDIUM\n  -  github.com/old/removed-dep\n  ↑  github.com/escalated/dep                LOW → HIGH\n  ↓  github.com/improved/dep                HIGH → MEDIUM\n\n  added=1  removed=1  escalated=1  improved=1\n```\n\n**Change types:**\n- `+` — new module (not in previous snapshot)\n- `-` — removed module\n- `↑` — risk escalated (higher risk level or higher effective score)\n- `↓` — risk improved (lower risk level or lower effective score)\n\n#### `gorisk history trend`\n\nPer-module score **sparkline table** showing how each module's effective risk score has evolved across the last 10 snapshots.\n\n```bash\ngorisk history trend\ngorisk history trend --module redis          # filter by module name substring\ngorisk history trend --json\n```\n\n**Text output:**\n\n```\nMODULE                              TREND (last 10)       FIRST  LAST  CHANGE\n────────────────────────────────────────────────────────────────────────────────\ngithub.com/redis/go-redis           ▁▂▂▃▃▃▅▆▆▇              12    45    +33  ↑\ngithub.com/stretchr/testify         ████████████              8     8      0  →\ngolang.org/x/net                    ▃▃▂▂▂▁▁▁▁▁              30    10    -20  ↓\n```\n\n**Sparkline:** 8 unicode block characters `▁▂▃▄▅▆▇█` represent score bands from 0–100.\n\n**`--json` output:**\n\n```json\n[\n  {\n    \"module\": \"github.com/redis/go-redis\",\n    \"scores\": [12, 18, 18, 24, 24, 24, 35, 40, 40, 45],\n    \"first_score\": 12,\n    \"last_score\": 45,\n    \"change\": 33\n  }\n]\n```\n\n---\n\n### `gorisk trace`\n\nRuntime execution tracing — instruments a Go package and records which capabilities are exercised at runtime (as opposed to statically detected).\n\n```bash\ngorisk trace \u003cpackage\u003e [args...]\ngorisk trace --timeout 10s github.com/foo/bar\ngorisk trace --json github.com/foo/bar\n```\n\n---\n\n### `gorisk version`\n\nPrint the gorisk version string.\n\n```bash\ngorisk version\n```\n\n---\n\n## Policy file\n\ngorisk can enforce rules automatically via a JSON policy file. Unknown fields are rejected at parse time.\n\n```json\n{\n  \"version\": 1,\n  \"fail_on\": \"high\",\n  \"min_health_score\": 0,\n  \"max_health_score\": 30,\n  \"block_archived\": false,\n  \"deny_capabilities\": [\"exec\", \"plugin\"],\n  \"allow_exceptions\": [\n    { \"package\": \"github.com/my/tool\", \"capabilities\": [\"exec\"] }\n  ],\n  \"max_dep_depth\": 0,\n  \"exclude_packages\": []\n}\n```\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `version` | int | Schema version — currently `1`. Unsupported versions are rejected at startup. |\n| `fail_on` | string | Fail threshold: `\"low\"`, `\"medium\"`, or `\"high\"` (default: `\"high\"`) |\n| `min_health_score` | int | Fail if any module's health score is below this (0 = disabled) |\n| `max_health_score` | int | Legacy field; kept for compatibility |\n| `block_archived` | bool | Fail if any dependency is archived on GitHub |\n| `deny_capabilities` | []string | Block any package with these capabilities (e.g. `[\"exec\", \"network\"]`) |\n| `allow_exceptions` | []object | Per-package exemptions from `deny_capabilities` |\n| `max_dep_depth` | int | Maximum allowed dependency depth (0 = unlimited) |\n| `exclude_packages` | []string | Packages to skip entirely during scan |\n\n**allow_exceptions schema:**\n\n```json\n{ \"package\": \"github.com/my/tool\", \"capabilities\": [\"exec\", \"network\"] }\n```\n\n---\n\n## Graph checksum\n\nEvery `gorisk scan` computes a short, deterministic SHA-256 digest of the dependency graph:\n\n```\ngraph checksum: a3f2b1c9d5e78f01\n```\n\nThe checksum covers: all non-main module paths and versions, all package import paths, capability sets, and dependency edges — all sorted for stability across runs.\n\n**Use case:** Detect silent graph changes between CI runs without diffing full output:\n\n```bash\n# Run on main\ngorisk scan --json | jq -r .graph_checksum \u003e checksum-main.txt\n\n# Run on PR branch\ngorisk scan --json | jq -r .graph_checksum \u003e checksum-pr.txt\n\n# Alert if different\ndiff checksum-main.txt checksum-pr.txt \u0026\u0026 echo \"graph unchanged\" || echo \"graph changed!\"\n```\n\n---\n\n## CI integration\n\n### GitHub Actions (official action)\n\n```yaml\n- uses: 1homsi/gorisk@main\n  with:\n    fail-on: high          # low | medium | high (default: high)\n    sarif: true            # upload to GitHub Security tab (default: true)\n    lang: auto             # auto | go | node (default: auto)\n    policy-file: ''        # optional path to policy.json\n```\n\n### Manual SARIF upload (GitHub Code Scanning)\n\n```yaml\n- name: gorisk scan\n  run: gorisk scan --sarif --lang auto \u003e gorisk.sarif || true\n\n- uses: github/codeql-action/upload-sarif@v4\n  with:\n    sarif_file: gorisk.sarif\n```\n\n### PR gate (GitHub Actions)\n\n```yaml\n- name: gorisk PR check\n  run: gorisk pr --lang auto\n```\n\n### Full CI pipeline example\n\n```yaml\nname: gorisk\non: [push, pull_request]\n\njobs:\n  gorisk:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-go@v5\n        with: { go-version: stable }\n\n      - name: Install gorisk\n        run: go install github.com/1homsi/gorisk/cmd/gorisk@latest\n\n      - name: Scan with policy\n        run: gorisk scan --policy .gorisk-policy.json --sarif \u003e gorisk.sarif\n\n      - uses: github/codeql-action/upload-sarif@v4\n        if: always()\n        with:\n          sarif_file: gorisk.sarif\n\n      - name: Record history snapshot\n        run: gorisk history record\n\n      - name: PR capability diff\n        if: github.event_name == 'pull_request'\n        run: gorisk pr\n```\n\n---\n\n## Output formats\n\nAll commands that produce structured output support `--json`. The `gorisk scan` command additionally supports `--sarif`.\n\n### `gorisk scan --json`\n\n```json\n{\n  \"graph_checksum\": \"a3f2b1c9d5e78f01\",\n  \"Capabilities\": [\n    {\n      \"Package\": \"golang.org/x/net/http2\",\n      \"Module\": \"golang.org/x/net\",\n      \"Capabilities\": { \"Score\": 15 },\n      \"RiskLevel\": \"MEDIUM\"\n    }\n  ],\n  \"Health\": [\n    {\n      \"Module\": \"golang.org/x/net\",\n      \"Version\": \"v0.25.0\",\n      \"Score\": 85,\n      \"Archived\": false,\n      \"CVECount\": 0,\n      \"CVEs\": null,\n      \"Signals\": { \"release_frequency\": 15, \"commit_age\": 0 }\n    }\n  ],\n  \"Passed\": true,\n  \"FailReason\": \"\"\n}\n```\n\n### `gorisk explain --json`\n\n```json\n[\n  {\n    \"package\": \"golang.org/x/net/http2\",\n    \"module\": \"golang.org/x/net\",\n    \"capability\": \"network\",\n    \"evidence\": [\n      {\n        \"file\": \"/path/to/h2_bundle.go\",\n        \"line\": 47,\n        \"context\": \"import \\\"net\\\"\",\n        \"via\": \"import\",\n        \"confidence\": 0.9\n      }\n    ]\n  }\n]\n```\n\n### `gorisk reachability --json`\n\n```json\n[\n  {\n    \"package\": \"golang.org/x/net/http2\",\n    \"reachable\": true,\n    \"risk\": \"HIGH\",\n    \"score\": 15,\n    \"capabilities\": [\"network\"]\n  }\n]\n```\n\n### `gorisk history trend --json`\n\n```json\n[\n  {\n    \"module\": \"github.com/redis/go-redis\",\n    \"scores\": [12, 18, 24, 30, 45],\n    \"first_score\": 12,\n    \"last_score\": 45,\n    \"change\": 33\n  }\n]\n```\n\n---\n\n## Environment variables\n\n| Variable | Purpose |\n|----------|---------|\n| `GORISK_GITHUB_TOKEN` | GitHub personal access token for health scoring (higher API rate limits) |\n\nWithout a token, the GitHub API rate limit is 60 requests/hour. With a token, it is 5000/hour.\n\n---\n\n## Setup for development\n\n```bash\ngit clone https://github.com/1homsi/gorisk\ncd gorisk\nmake setup   # installs git hooks (runs golangci-lint on commit)\nmake build\nmake test\n```\n\n---\n\n## Contributing\n\nAdding a new language requires two steps:\n\n### 1. Graph loader — `internal/adapters/\u003clang\u003e/adapter.go`\n\nImplement the `Analyzer` interface:\n\n```go\ntype Analyzer interface {\n    Name() string\n    Load(dir string) (*graph.DependencyGraph, error)\n}\n```\n\nRegister it in `internal/analyzer/analyzer.go` → `ForLang()` switch and add a detection signal to `detect()`.\n\nTo add capability evidence, call `CapabilitySet.AddWithEvidence(cap, CapabilityEvidence{...})` instead of `Add()`:\n\n```go\ncs.AddWithEvidence(capability.CapExec, capability.CapabilityEvidence{\n    File:       filePath,\n    Line:       lineNo,\n    Context:    \"exec.Command(\",\n    Via:        \"callSite\",\n    Confidence: 0.60,\n})\n```\n\n### 2. Feature implementations\n\nEach feature package defines an interface + per-language structs:\n\n| Package | Interface | Example impl |\n|---------|-----------|--------------|\n| `internal/reachability` | `Analyzer` | `GoAnalyzer`, `NodeAnalyzer` |\n| `internal/upgrade` | `Upgrader` | `GoUpgrader`, `NodeUpgrader` |\n| `internal/upgrade` | `CapDiffer` | `GoCapDiffer`, `NodeCapDiffer` |\n| `internal/prdiff` | `Differ` | `GoDiffer`, `NodeDiffer` |\n\nThe `reachability.Analyzer` interface has two methods:\n\n```go\ntype Analyzer interface {\n    Analyze(dir string) ([]ReachabilityReport, error)\n    AnalyzeFrom(dir, entryFile string) ([]ReachabilityReport, error)\n}\n```\n\n`Analyze` should call `AnalyzeFrom(dir, \"\")`.\n\nRegister everything in `internal/analyzer/analyzer.go`:\n\n```go\nvar registry = map[string]LangFeatures{\n    \"go\":   { ... },\n    \"node\": { ... },\n    \"rust\": {  // your new entry\n        Upgrade:      upgrade.RustUpgrader{},\n        CapDiff:      upgrade.RustCapDiffer{},\n        PRDiff:       prdiff.RustDiffer{},\n        Reachability: reachability.RustAnalyzer{},\n    },\n}\n```\n\nThe capability taxonomy, all output formats, explain evidence, history tracking, and CLI flags come for free.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F1homsi%2Fgorisk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F1homsi%2Fgorisk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F1homsi%2Fgorisk/lists"}