Skip to content

0disoft/ohrisk

Repository files navigation

Ohrisk

Ohrisk catches open-source license risk before your PR ships.

It is a local CLI for developers who need a quick answer to questions like:

  • Did this dependency bring in AGPL, GPL, BUSL, or unknown license evidence?
  • Is the risky package production-relevant or dev-only?
  • Which parent package introduced the transitive risk?
  • Does the answer change for SaaS versus distributed app usage?

Ohrisk is a risk decision aid, not legal advice. It reports low, review, high, and unknown findings for the selected usage profile.

Quickstart

Install and run your first scan in under a minute:

npm install -g ohrisk@1.12.0
cd your-project
ohrisk scan

The terminal report shows findings sorted by severity:

Risks: 1 high, 1 review, 1 unknown, 2 low
  • high — replace or escalate before shipping
  • review — check before shipping under the selected profile
  • unknown — license evidence is missing or unrecognized
  • low — known low-risk license expression

Gate a production SaaS build by narrowing to production dependencies and the SaaS usage profile:

ohrisk scan --profile saas --prod

Open a browser-friendly HTML report:

ohrisk scan --html --output ohrisk-report.html --open

Scan a public GitHub repository without keeping a checkout. With --html and no --output, the report is written to the directory where you ran Ohrisk:

ohrisk scan --html https://github.com/0disoft/laqu.git
# writes ./laqu-ohrisk.html

Git submodules are not fetched. Their paths are skipped by default and the report is marked as incomplete; use --submodules reject when any submodule must fail the scan instead.

Symbolic links are never followed. Remote scans remove their checkout entries, continue with ordinary dependency inputs, and record bounded skipped paths as incomplete coverage. Regular files whose names cannot be represented portably on supported systems are also excluded before checkout and reported as incomplete coverage; path traversal and repository-control paths still fail the scan.

Remote scans automatically merge supported dependency inputs found at the same project root. Maven aggregator POMs are followed through bounded, project-contained <module> paths, so a root packaging=pom file does not produce an empty success while its child modules contain the actual dependencies.

Maven Central remains the default remote evidence source. A project-declared HTTPS Maven repository is contacted only when its exact hostname is allowed by policy or --allow-host; allowing a host does not bypass DNS, socket, redirect, response-size, checksum, or package-identity validation:

ohrisk scan --allow-host repo.papermc.io https://github.com/PlayPro/CoreProtect

Use Korean, Spanish, French, Chinese, Hindi, Japanese, Indonesian, Turkish, Russian, or German HTML report text when you want a local review artifact for those readers:

ohrisk scan --html --language ko --output ohrisk-report.html --open
ohrisk scan --html --language es --output ohrisk-report-es.html --open
ohrisk scan --html --language fr --output ohrisk-report-fr.html --open
ohrisk scan --html --language zh --output ohrisk-report-zh.html --open
ohrisk scan --html --language hi --output ohrisk-report-hi.html --open
ohrisk scan --html --language ja --output ohrisk-report-ja.html --open
ohrisk scan --html --language id --output ohrisk-report-id.html --open
ohrisk scan --html --language tr --output ohrisk-report-tr.html --open
ohrisk scan --html --language ru --output ohrisk-report-ru.html --open
ohrisk scan --html --language de --output ohrisk-report-de.html --open

Prefer not to install globally? Use npx ohrisk scan instead.

Ready to gate PRs? Run ohrisk ci --fail-on high locally, or see the GitHub Actions guide to wire it into CI.

When to use it

Run Ohrisk when you are about to add or upgrade a dependency and want a fast, local read on whether the license evidence introduces risk for your shipping model. It sits between "I just installed a package" and "legal review."

  • before opening a PR that adds or changes dependencies
  • before cutting a release or tagging a build
  • when a transitive dependency surprise appears in a lockfile diff
  • when you need a SARIF or SBOM artifact for a compliance pipeline

Ohrisk does not approve or block packages on its own. It gives you the evidence and a profile-aware severity so you can decide.

Commands

Command What it answers
ohrisk scan What does my dependency tree look like right now? Non-failing local decision aid.
ohrisk ci Should this PR fail the build? Runs a scan and exits non-zero when findings meet --fail-on.
ohrisk diff <ref> What changed since the baseline git ref? Separates new, meaningfully changed, and resolved findings.
ohrisk explain <expr> How would Ohrisk classify this license expression for a profile, optionally with license-level organization policy?

Usage profiles

Ohrisk evaluates the same dependency tree differently depending on how you ship software, because redistribution changes license obligations.

  • saas (default): you run the service and do not redistribute the package binaries to users. GPL-only copyleft such as GPL-2.0 and GPL-3.0 is treated as review rather than an immediate block, because SaaS usage does not trigger redistribution obligations. AGPL and source-available restrictions remain high. MPL file-level copyleft is low when no package copy is redistributed; LGPL and EPL remain review.
  • distributed-app: you ship the package to users. GPL becomes high because redistribution obligations apply. Weak copyleft (LGPL, MPL, EPL) is flagged as review.

Pick the profile that matches how the dependency reaches your users:

ohrisk scan --profile saas
ohrisk scan --profile distributed-app

Runtime

Ohrisk is distributed as an npm package, and the packaged CLI runs on Node.js >=24.0.0. Bun is used for Ohrisk development, tests, and packaging, but users do not need Bun installed to run the published CLI.

Ohrisk scans dependency-free package.json manifests, Bun, npm package-lock/shrinkwrap, pnpm, Deno npm, Yarn, Rust Cargo, Go modules and workspaces, Python pyproject/pylock/uv/Pipenv/PDM/Poetry/requirements.txt, Java Gradle lockfiles and version catalogs, Maven pom.xml, Bazel MODULE.bazel, .NET NuGet lockfiles, Conan locks, Conda environment specs and locks, vcpkg manifests, Haskell Stack locks, Perl Carton snapshots, LuaRocks locks, Dart/Flutter Pub locks, Terraform provider locks, Helm chart dependency metadata, Nix flake locks, Unity Package Manager locks, R renv locks, Julia manifests, SwiftPM pins, Carthage pins, CocoaPods locks, Elixir Mix locks, Erlang Rebar3 locks, Ruby Bundler lockfiles, and PHP Composer lockfiles, plus CycloneDX JSON/XML, SPDX JSON/RDF, and SPDX tag-value SBOM inputs, regardless of which package manager you use to install the CLI.

Current Scope

The current implementation is the first local dependency-risk vertical slice:

  • dependency-free package.json manifests, Bun bun.lock, npm package-lock.json, npm npm-shrinkwrap.json, pnpm pnpm-lock.yaml, Deno deno.lock, Rust Cargo.lock, Go go.work, Go go.mod, Python pyproject.toml, Python pylock.toml, named Python pylock.<name>.toml, Python uv.lock, Python Pipenv Pipfile.lock, Python PDM pdm.lock, Python poetry.lock, Python requirements.txt, Java Gradle gradle.lockfile, Java Gradle gradle/dependency-locks directories and gradle/dependency-locks/*.lockfile, Java Gradle gradle/libs.versions.toml, Java Maven pom.xml, Bazel MODULE.bazel, .NET NuGet packages.lock.json, .NET restored obj/project.assets.json, .NET NuGet packages.config, .NET *.csproj, Conan conan.lock, Conda environment.yml, Conda environment.yaml, Conda conda-lock.yml, Conda conda-lock.yaml, vcpkg vcpkg.json, Terraform .terraform.lock.hcl, Helm Chart.lock, Helm Chart.yaml, Nix flake.lock, Unity Package Manager Packages/packages-lock.json, R renv.lock, Julia Manifest.toml, Haskell Stack stack.yaml.lock, Perl Carton cpanfile.snapshot, LuaRocks luarocks.lock, Dart/Flutter pubspec.lock, SwiftPM Package.resolved, Carthage Cartfile.resolved, CocoaPods Podfile.lock, Elixir Mix mix.lock, Erlang Rebar3 rebar.lock, Ruby Bundler Gemfile.lock, PHP Composer composer.lock, CycloneDX JSON/XML, SPDX JSON/RDF, SPDX tag-value .spdx, and Yarn classic/Berry yarn.lock project discovery
  • Node-compatible packaged CLI entrypoint for npm, pnpm, Yarn, npx, pnpm dlx, and yarn dlx users
  • explicit dependency input selection with --lockfile <path> for projects that contain more than one supported input file
  • opt-in --all discovery that merges every supported input at one project root, deduplicates packages by Package URL, and preserves contributing-lockfile provenance
  • direct and transitive dependency graph extraction when the dependency input records parent/child relationships
  • Bun, npm, pnpm, and Yarn classic/Berry workspace projects are scanned from every workspace/importer package root
  • npm package-lock/shrinkwrap and pnpm traversal retain every reachable package and store at most 64 dependency paths per package with a typed truncation diagnostic
  • pnpm catalog: and catalog:<name> dependency specifiers are resolved from pnpm-workspace.yaml
  • Deno deno.lock projects are scanned for npm package dependencies recorded in npm: specifiers; root remote URL imports and JSR packages fail closed instead of being silently skipped
  • Rust Cargo.lock projects are scanned for crates, using adjacent Cargo.toml root dependencies plus literal and segment */? Cargo workspace member manifests such as crates/*, crates/app-*, tools/?li, and crates/*/plugins/* when available, honoring workspace exclude entries, crate.workspace = true dependency keys, workspace dependency package aliases, and table-form dependency sections such as [dependencies.foo]; traversal is iterative, retains every reachable crate, and stores at most 64 dependency paths per crate with a typed truncation diagnostic; remote scans fetch crates.io archives only from the fixed public artifact host and trust them only after Cargo.lock SHA-256 plus archive identity verification
  • Go go.work projects are scanned across workspace modules and apply workspace replace directives before module-level replacements; Go go.mod projects are scanned for required modules and Go replace directives, using adjacent go.sum entries as exact module-zip checksums rather than treating every historical checksum as a current dependency on Go 1.17 and later
  • Python pylock.toml and named pylock.<name>.toml projects are scanned for versioned PyPI package records and project-root-contained source-tree package records with local source metadata
  • Python pyproject.toml projects without a companion lockfile are scanned for exact PEP 621 name==version direct dependency pins
  • Python uv.lock projects are scanned for PyPI package dependencies, project-root-contained directory or editable package source records, and remote Git records resolved to a full immutable commit; immutable Git records remain in the graph with unavailable evidence and are never cloned or replaced with same-name PyPI evidence, while iterative graph traversal retains every reachable package and stores at most 64 dependency paths per package with a typed truncation diagnostic
  • Python PDM pdm.lock and poetry.lock projects are scanned for PyPI package dependencies recorded in the lockfile
  • Python Pipenv Pipfile.lock projects are scanned for exact ==version PyPI package entries and project-root-contained local path or editable source entries in the default and develop sections
  • Python PDM pdm.lock projects use adjacent pyproject.toml root dependencies when available, infer roots from lockfile dependency references otherwise, and scan project-root-contained local path or relative file: source records
  • Python requirements.txt files are scanned for pinned PyPI packages, project-root-contained local source entries, editable local source entries, nested -r requirement files, and exact -c constraint pins; pip-compile # via annotations restore direct/transitive package paths when every named parent is present
  • Java Gradle gradle.lockfile and legacy gradle/dependency-locks directory projects are scanned for Maven coordinates recorded in dependency locking output; explicit gradle/dependency-locks/*.lockfile files are also accepted. Java Gradle gradle/libs.versions.toml projects are scanned for exact Maven library aliases from compact notation, module plus exact version, or module plus version.ref
  • Java Maven pom.xml projects are scanned for direct dependencies with explicit, property-resolved, same-file dependencyManagement, local .m2/repository, or exact Maven Central parent/imported-BOM dependencyManagement versions; remote model resolution is limited to 32 identity-checked POMs of at most 2 MiB each and eight parent/BOM levels, and is reusable through the artifact cache in --offline mode
  • Bazel MODULE.bazel projects are scanned for direct bazel_dep entries with literal exact version strings; nodep repo_name = None entries, include() expansion, overrides, module extensions, and MODULE.bazel.lock resolution output fail closed instead of being partial-scanned
  • .NET NuGet packages.lock.json and restored obj/project.assets.json projects are scanned for direct and transitive package dependencies; .NET NuGet packages.config and *.csproj files are scanned for direct PackageReference and PackageDownload items, including central PackageVersion entries and exact PackageDownload ranges resolved from unconditional same-file properties
  • Conan 2 conan.lock projects are scanned for recipe references from requires, build_requires, and python_requires; Conan binary package IDs, settings, options, user/channel, and recipe revisions are not modeled in Package URLs yet
  • Conda environment.yml and environment.yaml projects are scanned for exact Conda name=version pins and exact pip name==version pins; Conda conda-lock.yml and conda-lock.yaml projects are scanned for resolved conda and pip package entries and are preferred when both an environment spec and conda-lock output are present; unpinned environment files are skipped only during repository-wide automatic discovery while explicit --lockfile selection remains strict
  • vcpkg vcpkg.json projects are scanned from installed vcpkg_installed/vcpkg/status records when available, or from exact top-level overrides when installed status is absent; baseline and version>= constraints are not treated as resolved package versions, and unresolved manifests are skipped only during repository-wide automatic discovery while explicit --lockfile selection remains strict
  • Terraform .terraform.lock.hcl projects are scanned for locked provider versions; provider constraints and platform hashes are not modeled in Package URLs yet
  • Helm Chart.lock and Chart.yaml projects are scanned for chart dependency entries; Chart.lock is preferred when both files are present
  • Nix flake.lock projects are scanned for reachable flake inputs from the root input graph; Nix derivation package graphs are not reconstructed
  • Unity Package Manager Packages/packages-lock.json projects are scanned for non-built-in package entries; Unity built-in modules, Packages/manifest.json without a lockfile, Asset Store .unitypackage archives, Addressables catalogs, and remote UPM registry metadata fetch are not scanned yet
  • R renv.lock projects are scanned for package records in the lockfile; adjacent root DESCRIPTION Depends, Imports, LinkingTo, Suggests, and Enhances fields are used for production/development root classification when available, while dependency parent graphs, remote CRAN/GitHub/Bioconductor artifact fetch, and Packrat lockfiles are not scanned yet
  • Julia Manifest.toml projects are scanned for versioned [[deps.Name]] records; unversioned standard libraries are skipped, adjacent Project.toml [deps] and test target [extras] entries are used for root/dev classification when available, and remote Julia registry or package server artifact fetch is not scanned yet
  • Haskell Stack stack.yaml.lock projects are scanned for completed Hackage package pins; local Stack package database license metadata is used when present, while snapshot package expansion, git/path extra-deps, direct/transitive graph reconstruction, and Hackage metadata fetch are not scanned yet
  • Perl Carton cpanfile.snapshot projects are scanned for Carton snapshot v1 distribution pins and dependency paths inferred from provides and requirements; local Carton cache archive META.json or META.yml license metadata is used when present, while MetaCPAN artifact fetch is not scanned yet
  • LuaRocks luarocks.lock projects are scanned for literal dependencies table package pins; local .rockspec files in the project root or local rocks tree are used for literal string or string-table license metadata when present, while dependency graph reconstruction and LuaRocks metadata fetch are not scanned yet
  • Dart and Flutter pubspec.lock projects are scanned for concrete Pub package versions recorded in the lockfile
  • Swift Package Manager Package.resolved projects are scanned for pinned packages with resolved versions, revisions, or branches; Package.resolved does not expose parent dependency graphs, so packages are reported as root-level pins with unknown dependency type
  • Carthage Cartfile.resolved projects are scanned for resolved GitHub, git, and binary pins; Cartfile.resolved does not expose parent dependency graphs, so packages are reported as root-level pins with unknown dependency type
  • CocoaPods Podfile.lock projects are scanned for resolved pods; subspecs are collapsed to their root pod identity and dependency type is reported as unknown because Podfile.lock does not encode production/development groups
  • Elixir Mix mix.lock projects are scanned for resolved Hex package pins; adjacent root mix.exs literal only: dependency options are used for production/development root classification when available, while mix.lock dependency graph reconstruction and remote Hex.pm artifact fetch are not scanned yet
  • Erlang Rebar3 rebar.lock projects are scanned for Hex pkg pins; depth-0 Hex pins are classified as production roots, while git/path deps, plugin locks, profile-specific test deps, and Rebar dependency tree reconstruction are not scanned yet
  • Ruby Bundler Gemfile.lock projects are scanned for direct and transitive gem dependencies
  • PHP Composer composer.lock projects are scanned for production and development package dependencies, using adjacent composer.json root dependencies when available and package license declarations embedded in the lockfile as local evidence
  • CycloneDX JSON/XML, SPDX JSON/RDF, and SPDX tag-value SBOM files are scanned for Package URL-backed package identities, dependency relationships, and embedded license evidence
  • explicit --lockfile SBOM paths are sniffed by content when their filename does not use a supported SBOM name or suffix
  • npm alias dependency resolution, including pnpm alias package keys, with alias context preserved in dependency paths
  • production, development, optional, and peer dependency classification
  • local file: package artifact evidence
  • installed node_modules package evidence, including npm alias install names, before network fallback
  • Yarn Berry .yarn/cache package zip evidence before registry fallback for PnP installs without node_modules
  • checksum- and identity-verified local Cargo registry source or vendor/<crate> evidence before a fixed public crates.io artifact fallback; remote .crate archives are trusted only after their exact Cargo.lock SHA-256, package root, and Cargo.toml identity are verified
  • local Go module cache, vendor/<module>, and project-root-contained local replace path evidence before a fixed public Go module proxy fallback for go.work and go.mod scans; remote module ZIPs are trusted only after their exact go.sum h1 checksum is verified
  • Python .venv and venv *.dist-info/METADATA package evidence, plus project-root-contained local source metadata and license files for uv.lock, pylock.toml, requirements.txt, Pipfile.lock, and pdm.lock local source entries, before unavailable fallback
  • exact-version PyPI release evidence for locked Python packages, using SHA-256-verified source distributions or wheels plus identity-checked PKG-INFO, METADATA, and license files; a verified distribution that exceeds archive inspection limits remains unknown without aborting unrelated package findings
  • local Maven .m2/repository POMs for Maven parent/BOM version management and package license evidence, including bounded parent-POM license inheritance
  • exact Maven Central parent/imported-BOM POMs for dependency-version resolution during filesystem scan and ci runs, with a 32-document scan limit, 2 MiB per-POM limit, eight-level model depth, exact identity checks, cache reuse, and fail-closed incomplete-model handling
  • exact-version Maven Central POM evidence for Maven and Gradle coordinates when local POM evidence is unavailable, plus project-declared HTTPS repositories whose exact hosts are explicitly allowed; all POM requests retain bounded parent inheritance, cache, offline, timeout, redirect, identity, and response-size checks
  • checksum-verified Maven JAR license-file fallback when the selected POM chain has no license name, requiring a same-repository SHA-256 sidecar and exact embedded META-INF/maven/<groupId>/<artifactId>/pom.properties identity before root or META-INF license files are trusted
  • Bazel module license evidence uses local Bazel registry local_path sources from file-based registries when present; remote Bazel registry metadata fetching is not scanned yet
  • local NuGet package cache .nuspec evidence before unavailable fallback for packages.lock.json, obj/project.assets.json, packages.config, and *.csproj packages
  • local Conan cache conanfile.py metadata and package source license evidence before unavailable fallback for conan.lock recipes
  • local Conda package cache info/index.json metadata and license files before unavailable fallback for environment.yml, environment.yaml, conda-lock.yml, and conda-lock.yaml Conda packages
  • local vcpkg vcpkg_installed/<triplet>/share/<port>/copyright evidence before unavailable fallback for vcpkg.json packages
  • local Terraform .terraform/providers license file evidence before unavailable fallback for .terraform.lock.hcl providers
  • local Helm charts/ Chart.yaml metadata and license file evidence before unavailable fallback for Chart.lock and Chart.yaml dependencies
  • local Nix path input license file evidence before unavailable fallback for flake.lock inputs
  • local Unity Packages/ and Library/PackageCache package source evidence before unavailable fallback for Packages/packages-lock.json packages
  • local R renv/library DESCRIPTION metadata and license file evidence before unavailable fallback for renv.lock packages
  • local Julia depot Project.toml metadata and license file evidence before unavailable fallback for Manifest.toml packages
  • local Stack .stack-work/install package database metadata before unavailable fallback for Hackage packages
  • local Carton cache archive META.json or META.yml metadata before unavailable fallback for CPAN distributions
  • local Dart Pub cache package source evidence before unavailable fallback for pubspec.lock packages
  • local SwiftPM .build/checkouts and Xcode SourcePackages/checkouts package source evidence before unavailable fallback for Package.resolved packages
  • local Carthage Carthage/Checkouts package source evidence before unavailable fallback for Cartfile.resolved packages
  • local CocoaPods Pods/<pod> source and Pods/Local Podspecs/<pod>.podspec.json evidence before unavailable fallback for Podfile.lock packages
  • local Elixir/Erlang deps/<package> source and mix.exs or rebar.config license metadata before unavailable fallback for Hex packages
  • local Bundler/RubyGems install path gemspec evidence before unavailable fallback for Gemfile.lock gems
  • Composer composer.lock package license declarations, followed by local vendor/<vendor>/<package>/composer.json evidence when lockfile metadata is absent
  • remote HTTPS package tarball evidence when the lockfile points to a tarball with supported integrity metadata, with plaintext HTTP, credential-bearing URLs, obvious local, private, special-purpose, and DNS-resolved internal hosts blocked before fetch, connected socket addresses rechecked by the default fetcher, redirects followed only after each target is validated, and transient network failures recorded as unavailable package evidence so other packages can still be scanned
  • fixed proxy.golang.org module ZIP evidence for Go dependencies with an exact go.sum h1 checksum, including official storage.googleapis.com redirects, one bounded transient-failure retry, full module-ZIP checksum verification, and root license-file inspection without npm credentials
  • a shared content-addressed artifact cache with HTTP freshness metadata, conditional ETag/Last-Modified revalidation, valid stale-entry support in --offline mode, and automatic 2 GiB LRU trimming
  • ohrisk cache status|prune|clear commands for cache inspection, age/size cleanup, and bounded removal
  • lockfile integrity verification for local and remote package tarballs; remote tarballs without integrity are reported as unavailable instead of being trusted as license evidence
  • npm registry metadata lookup when the lockfile does not include a direct tarball URL
  • exact npm registry SPDX metadata for transitive packages, with direct dependencies and missing or non-SPDX declarations still inspected from integrity-verified tarballs
  • exact SPDX license metadata embedded by npm in modern package-lock.json and npm-shrinkwrap.json; missing, conflicting, or non-SPDX declarations continue to local or verified-tarball evidence
  • PyPI exact-release metadata lookup when a locked Python package does not include a direct distribution URL
  • gzipped package tarball evidence
  • package.json license fields
  • Cargo Cargo.toml package license fields
  • Python METADATA License-Expression, License, and recognized license classifier fields
  • Maven POM <licenses> names from the package POM or a bounded inherited parent POM
  • NuGet .nuspec <license> expressions
  • Ruby gemspec license and licenses fields
  • Composer package composer.json license fields
  • CycloneDX JSON/XML and SPDX JSON/RDF/tag-value package license declarations from SBOM metadata
  • common root-level LICENSE, LICENCE, UNLICENSE, COPYING, and NOTICE file variants
  • medium-confidence standard license detection from recognizable LICENSE and COPYING file text, including SPDX identifiers, GPL-family v2/v3 text, Zlib text, public-domain-style text, and malformed metadata pointers
  • SPDX-like license expression parsing
  • common human-readable license metadata alias normalization, including slash and comma dual-license shorthands
  • low-risk classification for common permissive, Zlib, and public-domain-style SPDX licenses
  • NOTICE evidence is surfaced as attribution-preservation action text without raising severity
  • high-risk classification for common source-available restriction licenses
  • package-scoped commercial restriction detection in license evidence and package metadata; restrictions explicitly scoped to documentation or data/corpora remain visible as scoped evidence without raising the package-code severity
  • profile-aware risk evaluation for saas and distributed-app
  • terminal, JSON, and HTML reports
  • SARIF 2.1.0 reports for code scanning upload
  • waived findings in SARIF output as externally suppressed results
  • Markdown reports for PR comments and release notes
  • browser-friendly self-contained HTML reports for local review, with runtime search indexing and exact on-demand reconstruction of compacted long fingerprints so large monorepo reports do not duplicate multi-megabyte paths
  • CycloneDX 1.5 JSON SBOM reports with dependency relationships and Ohrisk risk decision properties
  • stable finding IDs for PR comments and local waiver workflows
  • local .ohrisk-waivers.json waivers by finding ID or fingerprint
  • stable diff matching that uses finding IDs and fingerprints to separate new, changed, and resolved findings without being triggered by action prose churn
  • exact finding fingerprints for SARIF partial fingerprints and audit trails
  • finding fingerprints in terminal and Markdown reports for waiver and audit workflows
  • structured dependency type and direct/transitive scope in findings
  • report file output with project-relative --output <file> paths
  • optional browser opening for written HTML reports with --open through a temporary 127.0.0.1 URL
  • command-specific help with ohrisk help <command> and ohrisk <command> --help
  • standalone license expression explanation
  • git ref diff reports that separate new, meaningfully changed, and resolved findings
  • diff --all comparison of independently discovered current and baseline input sets, including added and removed lockfiles
  • strict Draft 2020-12 JSON Schemas for scan, diff, and explain reports, with shared nested definitions and release-time output validation
  • JSON threshold outcomes for ci --fail-on and diff --fail-on
  • terminal and Markdown threshold outcomes for ci --fail-on and diff --fail-on
  • strict CI waiver drift checks for expired or unmatched local waivers
  • raw scan and CI mode with --no-waivers when waiver files should be ignored
  • explicit waiver mode in JSON, terminal, Markdown, HTML, and SARIF reports
  • explicit waiver mode in CycloneDX SBOM metadata

Central approval workflows, GitHub App checks, Go go.work use paths outside the project root, Go local replace paths outside the project root, pre-1.17 Go module graph reconstruction beyond the conservative go.sum fallback reconstruction, unpinned or short-reference uv remote VCS entries, Pipenv and PDM remote VCS entries, uv, Pipenv, and PDM local source paths outside the project root, remote VCS requirements.txt entries, unpinned requirements ranges without exact constraint pins, remote parent/BOM model resolution from project-declared or alternate Maven repositories, remote Maven model resolution for diff and archive inputs, Maven transitive graph resolution, unapproved project-declared Maven repositories, Gradle graph reconstruction, Gradle version catalog rich versions, bundle aliases, plugin aliases, and usage-site configuration reconstruction, Bazel MODULE.bazel include() expansion, Bazel overrides, module extensions, MODULE.bazel.lock graph reconstruction, remote Bazel registry metadata fetching, Conan 1 graph lock support, Conan binary package ID and remote ConanCenter artifact fetching, unpinned or ranged Conda environment.yml specs, Conda environment transitive dependency reconstruction, explicit per-platform conda-<platform>.lock exports, remote Conda channel artifact fetching, Conda build/channel/subdir Package URL qualifiers, Terraform module scanning, remote Terraform Registry metadata fetching, Helm transitive chart graph reconstruction, remote Helm repository chart fetching, Nix derivation package graph reconstruction, Nixpkgs package license extraction, vcpkg baseline-only resolution without installed status, vcpkg feature/platform selection reconstruction, remote vcpkg registry metadata fetching, SwiftPM parent graph reconstruction, Carthage parent graph reconstruction, remote Swift package checkout fetching, Carthage remote checkout or binary framework license fetching, CocoaPods remote podspec or source fetching, Mix and Rebar3 dependency graph reconstruction, Rebar3 git/path deps, Rebar3 plugin locks, remote Hex.pm artifact fetching, Composer plugin/platform repository resolution, alternate Cargo registries and Cargo Git/path source fetching, arbitrary or private Go proxies, Maven source archives or JARs without a same-repository SHA-256 sidecar and embedded exact identity, NuGet, pub.dev, RubyGems, or Packagist artifact fetching are not part of this slice yet.

Usage

Install with another package manager if you do not want npm:

pnpm add -g ohrisk
yarn global add ohrisk
bun add -g ohrisk

Run once with a package-manager exec command:

pnpm dlx ohrisk scan
yarn dlx ohrisk scan
bunx ohrisk scan

Run a local scan from a supported project:

ohrisk scan

Scan a public GitHub repository through a temporary shallow clone:

ohrisk scan --html https://github.com/0disoft/laqu.git
ohrisk scan --repo https://github.com/0disoft/laqu.git --json
ohrisk scan --html https://github.com/Mbed-TLS/mbedtls.git
ohrisk scan --submodules reject https://github.com/0disoft/laqu.git

The first command writes laqu-ohrisk.html in the current invocation directory. Only public https://github.com/<owner>/<repository>[.git] URLs are accepted. The remote scan path requires a Git executable available on PATH. Git submodules are never fetched: the default --submodules ignore mode skips their gitlinks and records bounded paths plus incomplete coverage in every report, while --submodules reject preserves strict failure. Private credentials, other hosts or protocols, unsafe or oversized trees, and --offline are rejected. Symbolic links are removed without following their targets and recorded as incomplete coverage. Non-portable regular paths are excluded before checkout and reported separately. The temporary checkout is removed after the scan. Policy, waivers, cache, and report output stay rooted in the directory where Ohrisk was invoked; checkout-local policy and waivers are never trusted. When the repository root has no supported input, Ohrisk recursively selects one nested dependency project or merges multiple nested project roots into one repository-wide graph with per-lockfile provenance. Automatic fan-out is capped at 64 project roots and 128 inputs; --lockfile <relative-path> narrows the scan explicitly. Absolute and traversal paths are rejected. Remote repository input is supported by scan, not ci, diff, or the composite GitHub Action. See the CLI command contract for exact limits.

Scan a source archive without extracting it to disk:

ohrisk scan --archive artifacts/source.zip
ohrisk ci --archive artifacts/source.tar.gz --all --fail-on high

Archive mode is available for scan and ci, not diff. It accepts ZIP, TAR, TAR.GZ, and TGZ through a strict read-only in-memory reader; nested archives are not opened. --archive cannot be combined with --lockfile or --workspace-root, but it can be combined with --all. Policy and waiver files inside an archive are never auto-loaded: .ohrisk.yml and .ohrisk-waivers.json from the host invocation directory remain authoritative. Encrypted or ZIP64 archives, unsupported compression and entry types, unsafe paths, integrity failures, and archives over the documented resource limits are rejected. See the CLI command contract for the supported subset and exact limits.

Beginner HTML report flow on Windows PowerShell:

npm install -g ohrisk@1.12.0
ohrisk version
cd C:\path\to\your\project
ohrisk scan --html --output reports\ohrisk-report.html --open
ohrisk scan --html --language ko --output reports\ohrisk-report-ko.html --open
ohrisk scan --html --language es --output reports\ohrisk-report-es.html --open
ohrisk scan --html --language fr --output reports\ohrisk-report-fr.html --open
ohrisk scan --html --language zh --output reports\ohrisk-report-zh.html --open
ohrisk scan --html --language hi --output reports\ohrisk-report-hi.html --open
ohrisk scan --html --language ja --output reports\ohrisk-report-ja.html --open
ohrisk scan --html --language id --output reports\ohrisk-report-id.html --open
ohrisk scan --html --language tr --output reports\ohrisk-report-tr.html --open
ohrisk scan --html --language ru --output reports\ohrisk-report-ru.html --open
ohrisk scan --html --language de --output reports\ohrisk-report-de.html --open

The scan prints live terminal progress while it reads the project, collects license evidence, evaluates risk, and writes the report. In CI or redirected stderr, Ohrisk keeps the plain append-only progress lines so logs stay readable. When the scan succeeds, the terminal prints Wrote report to ... so you can see the exact saved file path. With --open, Ohrisk opens the report through a temporary 127.0.0.1 browser URL after the HTML file is written. If the browser does not open, the scan can still succeed; open the printed HTML file path manually.

If a lockfile contains local file: package dependencies that point to sibling packages outside the current project repository, keep the default fail-closed boundary and declare the monorepo/workspace root explicitly:

ohrisk scan --workspace-root .. --html --output reports/ohrisk-report.html
ohrisk ci --workspace-root C:\path\to\workspace --fail-on high
ohrisk diff main --workspace-root .. --prod

--workspace-root must point to an existing directory. Local package evidence is then trusted only when the resolved artifact stays inside the project, repository root, or that explicit workspace root. Local packages marked "private": true in package.json are treated as internal package evidence when they omit public license metadata, so they do not appear as unknown external OSS findings solely because they have no license field.

Print command help or the installed package version:

ohrisk help
ohrisk help scan
ohrisk version

Supported dependency input files:

  • dependency-free package.json manifests, reported as an empty dependency graph
  • bun.lock
  • package-lock.json with either a modern packages section or an npm v1 dependency tree
  • npm-shrinkwrap.json with the same package-lock parser support
  • pnpm-lock.yaml with importers, packages, and snapshots sections, including default and named catalogs from pnpm-workspace.yaml
  • deno.lock npm package entries from Deno v3/v4-style lockfiles
  • Cargo.lock crate entries from Rust Cargo projects, using adjacent Cargo.toml root dependencies plus literal and segment */? Cargo workspace member manifests such as crates/*, crates/app-*, tools/?li, and crates/*/plugins/* when available, honoring workspace exclude entries, crate.workspace = true dependency keys, workspace dependency package aliases, and table-form dependency sections such as [dependencies.foo], plus local Cargo registry source for evidence
  • go.work workspace modules, workspace replace directives, module go.mod requirements, module replace directives, and adjacent go.sum ZIP checksums when available, using local Go module cache, vendor/<module>, project-root-contained local replacement paths, or checksum-verified public Go module ZIP evidence
  • go.mod module requirements and Go replace directives, using adjacent go.sum ZIP checksums for local or fixed-public-proxy evidence; Go 1.17 and later use the complete go.mod requirement graph while earlier or versionless modules conservatively retain go.sum-only fallback nodes
  • pylock.toml and pylock.<name>.toml versioned package entries and project-root-contained source-tree records from the PyPA lockfile specification, using dependency references for audit paths and installed .venv/venv dist-info metadata or local source metadata and license files for local evidence
  • pyproject.toml exact PEP 621 direct dependency pins such as name==version, using installed .venv/venv dist-info metadata for local evidence
  • uv.lock package entries from Python uv projects plus project-root-contained directory and editable records and remote Git records resolved to a full immutable commit, using installed .venv/venv dist-info metadata or local source metadata for ordinary packages while immutable Git records stay unknown until their exact commit license is verified manually
  • Pipfile.lock exact ==version entries and project-root-contained local path or editable source entries from Python Pipenv projects, using installed .venv/venv dist-info metadata or local source metadata and license files for local evidence
  • pdm.lock package entries and project-root-contained local path or relative file: source records from Python PDM projects, using adjacent pyproject.toml root dependencies when available and installed .venv/venv dist-info metadata or local source metadata and license files for local evidence
  • poetry.lock package entries from Python Poetry projects, using adjacent pyproject.toml root dependencies when available and installed .venv/venv dist-info metadata for local evidence
  • pinned requirements.txt entries such as name==version, local source entries such as -e ./local-package, ./local-package, file:./local-package, and name @ file:./local-package, nested -r requirement files, and exact -c constraint pins for ranged entries, using pip-compile # via annotations for bounded dependency-path reconstruction and installed .venv/venv dist-info metadata or project-root-contained local source metadata and license files for local evidence
  • gradle.lockfile, legacy gradle/dependency-locks directories, and explicit gradle/dependency-locks/*.lockfile Maven coordinates from Java Gradle dependency locking, using local .m2/repository POM metadata or exact-version Maven Central POM fallback for evidence
  • gradle/libs.versions.toml Maven library aliases with exact versions or version.ref values from the same catalog, using local .m2/repository POM metadata or exact-version Maven Central POM fallback for evidence
  • Maven pom.xml direct dependencies with explicit versions or versions resolved from local <properties>, same-file dependencyManagement, local .m2/repository, or exact Maven Central parent/imported-BOM dependencyManagement during filesystem scan and ci; reactor-internal module dependencies are excluded, and package licenses use local POM metadata, Maven Central, or explicitly allowed project-declared repositories with bounded parent inheritance and checksum/identity-verified JAR fallback
  • Bazel MODULE.bazel direct bazel_dep entries with literal exact versions, failing closed on graph-expanding constructs and using local Bazel registry local_path source evidence when present
  • .NET NuGet packages.lock.json package entries, restored obj/project.assets.json package graph entries, packages.config package entries, and direct *.csproj PackageReference or PackageDownload entries, resolving central versions from the nearest Directory.Packages.props and exact PackageDownload ranges from unconditional same-file properties when present, and using local NuGet cache .nuspec metadata for evidence
  • Conan 2 conan.lock recipe references from requires, build_requires, and python_requires, using local Conan cache conanfile.py metadata and license files for evidence
  • Conda environment.yml and environment.yaml exact package pins plus Conda conda-lock.yml and conda-lock.yaml package entries, using local Conda package cache info/index.json metadata and license files for Conda package evidence
  • vcpkg vcpkg.json manifest dependencies resolved from installed vcpkg_installed/vcpkg/status records or exact top-level overrides, using installed vcpkg_installed/<triplet>/share/<port>/copyright files for evidence
  • Terraform .terraform.lock.hcl provider entries, using local .terraform/providers license files for evidence
  • Helm Chart.lock and Chart.yaml chart dependency entries, using local charts/ Chart.yaml metadata and license files for evidence
  • Nix flake.lock reachable flake input entries, using local path input license files for evidence
  • Unity Package Manager Packages/packages-lock.json package entries, using local Packages/ and Library/PackageCache source for evidence
  • R renv.lock package records, using adjacent root DESCRIPTION dependency fields for production/development classification and local renv/library package source and DESCRIPTION metadata for evidence
  • Julia Manifest.toml versioned package records, using local Julia depot package source and Project.toml metadata for evidence
  • Haskell Stack stack.yaml.lock completed Hackage package pins, using local Stack package database metadata before unavailable evidence fallback
  • Perl Carton cpanfile.snapshot distribution pins, using local Carton cache archive metadata before unavailable evidence fallback
  • LuaRocks luarocks.lock dependency pins, using local .rockspec license metadata before unavailable evidence fallback
  • Dart/Flutter pubspec.lock package entries, using local .dart_tool/package_config.json and Pub cache package source for evidence
  • SwiftPM Package.resolved package pins, using local .build/checkouts or SourcePackages/checkouts package source for evidence
  • Carthage Cartfile.resolved package pins, using local Carthage/Checkouts package source for evidence
  • CocoaPods Podfile.lock pod entries, using local Pods/ source and Pods/Local Podspecs metadata for evidence
  • Elixir Mix mix.lock Hex package pins, using adjacent root mix.exs literal only: options for production/development classification and local deps/ package source and mix.exs metadata for evidence; Erlang Rebar3 rebar.lock Hex package pins, using depth-0 production root classification and local deps/ package source and rebar.config metadata for evidence
  • Ruby Bundler Gemfile.lock gem entries, using literal companion Gemfile group blocks and inline group: options for development classification and local Bundler/RubyGems gemspec metadata for evidence
  • PHP Composer composer.lock package entries, using adjacent composer.json root dependencies when available, embedded lockfile licenses first, and local vendor/ package metadata when lockfile license metadata is absent
  • CycloneDX JSON/XML SBOM package entries with Package URL identities, dependency relationships, and embedded license evidence; traversal is iterative, retains every reachable component, stores at most 64 paths per component, and summarizes paths deeper than 256 components with typed graph diagnostics
  • SPDX JSON/RDF and tag-value SBOM package entries with Package URL external refs, dependency relationships, and embedded license evidence
  • Yarn classic/Berry yarn.lock with root and workspace dependency sets from package.json manifests, plus local .yarn/cache zip evidence for Berry/PnP installs

Select a specific dependency input when a project contains more than one supported input file:

ohrisk scan --lockfile package-lock.json
ohrisk scan --lockfile npm-shrinkwrap.json
ohrisk ci --lockfile pnpm-lock.yaml --fail-on high
ohrisk scan --lockfile deno.lock
ohrisk scan --lockfile Cargo.lock
ohrisk scan --lockfile go.work
ohrisk scan --lockfile go.mod
ohrisk scan --lockfile pylock.toml
ohrisk scan --lockfile pyproject.toml
ohrisk scan --lockfile uv.lock
ohrisk scan --lockfile Pipfile.lock
ohrisk scan --lockfile pdm.lock
ohrisk scan --lockfile poetry.lock
ohrisk scan --lockfile requirements.txt
ohrisk scan --lockfile gradle.lockfile
ohrisk scan --lockfile gradle/dependency-locks
ohrisk scan --lockfile gradle/dependency-locks/runtimeClasspath.lockfile
ohrisk scan --lockfile gradle/libs.versions.toml
ohrisk scan --lockfile pom.xml
ohrisk scan --lockfile MODULE.bazel
ohrisk scan --lockfile packages.lock.json
ohrisk scan --lockfile obj/project.assets.json
ohrisk scan --lockfile packages.config
ohrisk scan --lockfile MyApp.csproj
ohrisk scan --lockfile conan.lock
ohrisk scan --lockfile environment.yml
ohrisk scan --lockfile conda-lock.yml
ohrisk scan --lockfile vcpkg.json
ohrisk scan --lockfile .terraform.lock.hcl
ohrisk scan --lockfile Chart.lock
ohrisk scan --lockfile Chart.yaml
ohrisk scan --lockfile flake.lock
ohrisk scan --lockfile Packages/packages-lock.json
ohrisk scan --lockfile renv.lock
ohrisk scan --lockfile Manifest.toml
ohrisk scan --lockfile stack.yaml.lock
ohrisk scan --lockfile cpanfile.snapshot
ohrisk scan --lockfile luarocks.lock
ohrisk scan --lockfile pubspec.lock
ohrisk scan --lockfile Package.resolved
ohrisk scan --lockfile Cartfile.resolved
ohrisk scan --lockfile Podfile.lock
ohrisk scan --lockfile mix.lock
ohrisk scan --lockfile rebar.lock
ohrisk scan --lockfile Gemfile.lock
ohrisk scan --lockfile composer.lock
ohrisk scan --lockfile cyclonedx.json
ohrisk scan --lockfile licenses.cdx.json
ohrisk scan --lockfile cyclonedx.xml
ohrisk scan --lockfile sbom.cdx.xml
ohrisk scan --lockfile spdx.json
ohrisk scan --lockfile licenses.spdx.json
ohrisk scan --lockfile spdx.rdf
ohrisk scan --lockfile sbom.spdx.rdf.xml
ohrisk scan --lockfile sbom.spdx
ohrisk diff main --lockfile bun.lock
ohrisk diff main --all

Pick the usage profile:

ohrisk scan --profile saas
ohrisk scan --profile distributed-app

Limit the scan to production-relevant dependencies by excluding development-only packages:

ohrisk scan --prod

Print machine-readable output:

ohrisk scan --json

Print SARIF output for code scanning upload:

ohrisk scan --sarif

Print a Markdown report:

ohrisk scan --markdown --prod

Print a CycloneDX SBOM:

ohrisk scan --cyclonedx --prod

Write browser, SARIF, SBOM, or PR reports to files:

ohrisk scan --html --output reports/ohrisk.html --open
ohrisk scan --html --language ko --output reports/ohrisk-ko.html --open
ohrisk scan --html --language es --output reports/ohrisk-es.html --open
ohrisk scan --html --language fr --output reports/ohrisk-fr.html --open
ohrisk scan --html --language zh --output reports/ohrisk-zh.html --open
ohrisk scan --html --language hi --output reports/ohrisk-hi.html --open
ohrisk scan --html --language ja --output reports/ohrisk-ja.html --open
ohrisk scan --html --language id --output reports/ohrisk-id.html --open
ohrisk scan --html --language tr --output reports/ohrisk-tr.html --open
ohrisk scan --html --language ru --output reports/ohrisk-ru.html --open
ohrisk scan --html --language de --output reports/ohrisk-de.html --open
ohrisk scan --sarif --output reports/ohrisk.sarif
ohrisk scan --cyclonedx --output reports/ohrisk.cdx.json
ohrisk diff main --all --prod --markdown --output reports/ohrisk-pr.md

--output accepts project-relative file paths only. Absolute paths, drive-relative paths, UNC paths, and . or .. path segments are rejected.

Fail a local CI step when findings meet a threshold:

ohrisk ci --fail-on high

Fail a local CI step when waiver files contain expired or unmatched waivers:

ohrisk ci --strict-waivers

Run a raw audit scan or CI step without reading local waiver files:

ohrisk scan --no-waivers
ohrisk ci --no-waivers --fail-on high

Waive a finding locally by ID or fingerprint in .ohrisk-waivers.json:

{
  "waivers": [
    {
      "id": "agpl-child@0.1.0::production::transitive::fixture-bun-project>permissive-parent@1.0.0>agpl-child@0.1.0",
      "reason": "Accepted for this release after internal review.",
      "expiresOn": "2026-09-30"
    }
  ]
}

Waived findings are excluded from ci --fail-on threshold failures, but scan and CI JSON, terminal, Markdown, HTML, and SARIF reports still show them. Terminal, Markdown, and HTML reports include finding fingerprints so waiver files can target either id or fingerprint. Expired waivers and unmatched active waivers are reported separately in JSON, terminal, Markdown, and HTML reports and are not applied. ci --strict-waivers exits non-zero when either expired or unmatched waivers are present, even if active findings stay below the --fail-on threshold. JSON, terminal, Markdown, HTML, and SARIF outputs include the strict waiver drift result when that option is enabled. scan --no-waivers and ci --no-waivers do not read or apply local waiver files; ci --no-waivers cannot be combined with --strict-waivers. Reports include a waiver mode field or summary line so raw audits can distinguish ignored waiver files from projects with no waivers. If package names, versions, paths, reasons, or evidence text contain finding delimiters such as ::, >, or |, Ohrisk percent-escapes those characters in the generated IDs and fingerprints to keep waiver matching unambiguous.

Explain a license expression without scanning a project:

ohrisk explain AGPL-3.0-only --profile saas
ohrisk explain "GPL-2.0-only WITH Classpath-exception-2.0" --policy .ohrisk.yml --json

Compare the current findings against a baseline git ref:

ohrisk diff main --prod
ohrisk diff main --prod --fail-on unknown
ohrisk diff main --prod --markdown

Baseline refs must be branch, tag, or commit-like names such as main, origin/main, release/v1.2.3, or a commit hash. Git rev syntax such as HEAD@{1}, HEAD~1, and main:path is rejected. diff --all discovers and merges the current and baseline input sets independently, then reports added and removed lockfiles alongside separate new, changed, and resolved findings.

Inspect or clean the shared artifact cache:

ohrisk cache status
ohrisk cache prune --max-age 7d --max-size 1GiB
ohrisk cache clear

The default cache follows the platform cache directory, uses a 24-hour fallback TTL, conditionally revalidates expired HTTP entries, and automatically trims least-recently-used content above 2 GiB. Add --json for machine-readable cache command output or --cache-dir <path> to target an explicit cache.

Print the package version:

ohrisk --version

Report Shape

The terminal report is designed to show the highest-risk findings first:

Ohrisk scan
Profile: saas
Production only: yes
Risks: 1 high, 1 review, 1 unknown, 2 low
Waiver mode: local (.ohrisk-waivers.json)
Waived: 0 applied, 0 expired, 0 unmatched

Findings:
- [high] agpl-child@0.1.0
  id: agpl-child@0.1.0::production::transitive::fixture-bun-project>permissive-parent@1.0.0>agpl-child@0.1.0
  fingerprint: agpl-child@0.1.0::production::transitive::fixture-bun-project>permissive-parent@1.0.0>agpl-child@0.1.0::high::replace::License expression is high risk for saas.::license: AGPL-3.0-only|dependency: production|transitive dependency|source: local|package.json license: AGPL-3.0-only|file: COPYING (copying)
  License expression is high risk for saas.
  recommendation: replace
  action: Replace this package or escalate before shipping.
  dependency: production transitive
  path: fixture-bun-project -> permissive-parent@1.0.0 -> agpl-child@0.1.0
  evidence: license: AGPL-3.0-only; dependency: production; transitive dependency; source: local; package.json license: AGPL-3.0-only; file: COPYING (copying)

JSON output reuses the same finding model:

{
  "status": "profile_risk_evaluated",
  "profile": "saas",
  "prodOnly": true,
  "waiverMode": "local",
  "nextAction": "Replace or escalate high-risk dependencies before shipping.",
  "failOn": "high",
  "failed": true,
  "failingFindingCount": 1,
  "findings": [
    {
      "id": "agpl-child@0.1.0::production::transitive::fixture-bun-project>permissive-parent@1.0.0>agpl-child@0.1.0",
      "fingerprint": "agpl-child@0.1.0::production::transitive::fixture-bun-project>permissive-parent@1.0.0>agpl-child@0.1.0::high::replace::License expression is high risk for saas.::license: AGPL-3.0-only|dependency: production|transitive dependency",
      "packageId": "agpl-child@0.1.0",
      "severity": "high",
      "reason": "License expression is high risk for saas.",
      "recommendation": "replace",
      "action": "Replace this package or escalate before shipping.",
      "dependencyType": "production",
      "dependencyScope": "transitive",
      "paths": [
        [
          "fixture-bun-project",
          "permissive-parent@1.0.0",
          "agpl-child@0.1.0"
        ]
      ]
    }
  ]
}

Markdown output keeps the scan summary and PR-facing decision fields together:

- Licenses: `4 high-confidence`, `0 medium-confidence`, `1 low-confidence`
- License issues: `1 missing`, `0 malformed`
- Waiver mode: `local (.ohrisk-waivers.json)`
- Threshold: failed on high (1 finding at or above threshold)

| ID | Fingerprint | Severity | Package | Dependency | Reason | Recommendation | Action | Path |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `agpl-child@0.1.0::production::transitive::fixture-bun-project>permissive-parent@1.0.0>agpl-child@0.1.0` | `agpl-child@0.1.0::production::transitive::fixture-bun-project>permissive-parent@1.0.0>agpl-child@0.1.0::high::replace::License expression is high risk for saas.::license: AGPL-3.0-only\|dependency: production\|transitive dependency\|source: local\|package.json license: AGPL-3.0-only\|file: COPYING (copying)` | high | `agpl-child@0.1.0` | production transitive | License expression is high risk for saas. | replace | Replace this package or escalate before shipping. | fixture-bun-project -> permissive-parent@1.0.0 -> agpl-child@0.1.0 |

Risk Language

Ohrisk intentionally avoids legal safe or unsafe verdicts.

  • low: known low-risk license expression for the selected profile
  • review: review before shipping under the selected profile
  • high: replace or escalate before shipping under the selected profile, including explicit commercial-use restrictions and packages marked UNLICENSED
  • unknown: missing, malformed, or unrecognized license evidence

For example, GPL is treated differently for saas and distributed-app because redistribution changes the risk profile.

Development

Run the test suite:

bun test

Run the release-ready local gate:

bun run verify:release

Run the fixture scan manually:

cd test/fixtures/bun-project
bun run ../../../src/cli/main.ts scan --profile saas

Documentation

About

Catch open-source license compliance risks and generate profile-aware SBOM/SARIF reports locally.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Sponsor this project

 

Packages

 
 
 

Contributors