Hooray is an automation-first software security analysis and policy-enforcement engine. It builds a normalized dependency inventory from projects, CycloneDX SBOMs, archives, and OCI images; queries OSV for known vulnerabilities; adds license, secret, infrastructure-as-code, SAST, malware-indicator, and operational-risk findings; evaluates an auditable policy; stores scan history in SQLite; and renders results for humans, CI systems, and security platforms.
Hooray is designed for deterministic automation rather than silent risk suppression. Findings carry stable identifiers, evidence, confidence, applicability, remediation data, risk factors, and policy decisions. Operational failures are distinct from policy denials through separate exit codes.
Hooray accepts explicit input types or auto-detects them:
- project directories containing supported lockfiles or manifests;
- CycloneDX JSON SBOMs with nested components and dependency relationships;
- ZIP and TAR artifacts containing supported dependency files;
- OCI image-layout directories;
- OCI or Docker image TAR files; and
- CycloneDX JSON from standard input for
scan sbomandscan auto.
Inventory components retain package URLs, versions, scopes, provenance, locations, licenses, and dependency edges. Stable component, location, finding, and run identifiers make reports and history diffs reproducible.
- Deduplicates package URLs before network access.
- Queries OSV in batches of at most 1,000 packages and follows paginated batch results.
- Fetches each unique advisory once with bounded concurrency.
- Preserves advisory aliases, references, modified timestamps, fixed versions, and severity derived from OSV CVSS or ecosystem metadata.
- Classifies dependencies as direct, transitive, or disconnected and records up to 32 dependency paths with a maximum traversal depth of 128.
- Records applicability as
affected,not-affected,fixed,under-investigation, orunknown, with an evidence-based rationale. - Computes a transparent risk score from 0 to 10,000 using severity, confidence, applicability, dependency scope and directness, fix availability, component age, release cadence, and maintenance evidence.
- Selects the nearest higher fixed version, preferring a same-major release, and emits ecosystem-specific upgrade guidance for Cargo, npm/pnpm/Yarn, pip/Poetry, Go, Maven/Gradle, and NuGet when the required data is available.
--offline disables OSV access. Local inventory, license, filesystem, policy,
history, and report operations remain available; vulnerability findings are not
invented or served from an implicit cache.
Policies are strict YAML or TOML documents using schema version 1. Unknown
fields are rejected. Rules can select findings by:
- finding kind;
- minimum severity and confidence;
- applicability status;
- risk-score range;
- exact SPDX license expression;
- dependency scope;
- package-URL glob;
- rule-ID glob; and
- advisory-ID glob.
Rules are evaluated by descending priority and then rule ID. Outcomes are
allow, warn, or deny; if no rule matches, default_outcome applies.
Policies can fail closed when applicability or license data is unknown.
Exceptions are deliberately narrow and auditable. Every exception requires an ID, owner, reason, ticket, RFC 3339 expiry, and at least one exact selector. Exception selectors cannot contain globs. Optional compensating controls are recorded with the exception, and expired exceptions do not apply.
Example hooray-policy.yaml:
version: 1
fail_closed:
unknown_applicability: true
unknown_licenses: true
default_outcome: warn
rules:
- id: deny-critical-runtime
priority: 100
outcome: deny
reason: Critical runtime findings are release blocking
selectors:
minimum_severity: critical
scopes: [runtime]
- id: allow-mit
priority: 50
outcome: allow
reason: MIT is approved
selectors:
kinds: [license]
license_expressions: [MIT]
exceptions:
- id: temporary-ghsa-exception
owner: security@example.com
reason: Upgrade is being validated
ticket: SEC-1234
expires_at: "2026-08-01T00:00:00Z"
compensating_controls:
- Service is isolated from untrusted input
selectors:
advisory_id: GHSA-exampleValidate and evaluate policies independently:
hooray policy validate hooray-policy.yaml
hooray policy evaluate hooray-policy.yaml --run-id 'run:UUID' --format yamlHooray validates declared SPDX expressions and reports missing or invalid
license metadata. For project directories and OCI layouts, it also examines
bounded LICENSE, LICENCE, COPYING, NOTICE, and
THIRD-PARTY-NOTICES files without following symbolic links. Recognized text
signatures include MIT, Apache-2.0, GPL-3.0, LGPL-3.0, MPL-2.0, BSD-2-Clause,
BSD-3-Clause, ISC, BSL-1.0, and Unlicense. Detection is evidence, not legal
advice; policy should decide which expressions are acceptable for a deployment.
The Hooray repository itself is MIT licensed. Its dependency policy accepts
only the explicit MIT-compatible permissive SPDX allowlist in deny.toml:
Apache-2.0, Apache-2.0 WITH LLVM-exception, BSD-3-Clause,
CDLA-Permissive-2.0, ISC, MIT, Unicode-3.0, Unlicense, Zlib, and
bzip2-1.0.6. Unknown, copyleft, source-available, wildcard-version, yanked,
unknown-registry, and unknown-Git dependencies are rejected by the configured
checks; duplicate dependency versions are reported as warnings.
Filesystem analysis is bounded and deterministic. Project directories and OCI layouts are traversed without following symbolic links by default. Individual files, aggregate bytes, file count, traversal depth, archive metadata, and expanded archive inventory are subject to configured or built-in limits.
Secret detection covers AWS access-key IDs, GitHub and GitLab tokens, Slack
tokens, private-key headers, JWT-shaped values, and high-entropy credential
assignments. Placeholder-like values are ignored. Inline allowlist markers are
hooray:allow-secret, pragma: allowlist secret, gitleaks:allow, and
nosec. Secret values are never retained: evidence contains only redacted
classification data, length, entropy, and a SHA-256 fingerprint. Report
rendering also redacts values under sensitive key names.
IaC checks include:
- Terraform unrestricted ingress and explicitly disabled storage encryption;
- Dockerfile remote
ADD, secret-likeARG/ENV, and absence of an explicit non-rootUSER; - Kubernetes host networking, privileged containers, and privilege escalation;
- CloudFormation S3 buckets without public-access blocking and RDS instances without storage encryption.
SAST rules target concrete dangerous syntax in Rust, JavaScript/TypeScript, Python, Go, Java, and C#, including dynamic shell execution, dynamic evaluation, and formatted or concatenated SQL. These are focused static rules, not a compiler-complete data-flow engine.
Malware analysis supports exact caller-supplied SHA-256 signatures in the library API, executable/script polyglot indicators, embedded PE/ELF signatures, and metadata-only ZIP bomb heuristics. The CLI currently uses an empty local signature set, so it does not download or claim a malware-signature feed.
When provenance evidence supplies the relevant metadata, Hooray reports abandoned or unmaintained components, yanked or deprecated releases, stale release activity, and components excessively behind current releases. It does not infer these states without supporting evidence. Operational findings use the same policy, history, and risk-scoring model as other finding kinds.
Every completed CLI scan is saved to the configured SQLite database. History commands list runs, return complete reports, and diff introduced, resolved, and unchanged stable finding IDs:
hooray history list --limit 50 --offset 0 --format json
hooray history show 'run:UUID' --format yaml
hooray history diff 'run:PREVIOUS' 'run:CURRENT' --format json
hooray inventory --run-id 'run:UUID' --format json
hooray report 'run:UUID' --format html --output hooray-report.htmlA scan can compare itself with an explicit baseline. --new-findings-only
retains only findings absent from that baseline; without --baseline, it uses
the latest stored run and fails if none exists.
hooray scan project . --baseline 'run:UUID' --new-findings-only --format tableThe monitor service persists targets, inventory snapshots, advisory and policy digests, finding sets, and alert events in SQLite. It rescans only when source content changes, reevaluates when source/advisory/policy digests change, deduplicates events, retries delivery with bounded exponential backoff, dead-letters exhausted events, and prunes expired records. The current CLI notifier emits JSON alert events to standard error:
hooray monitor --once
hooray monitorMonitor targets must already exist in the database; the CLI does not provide a target-registration command.
Start the API server with:
hooray serveThe v1 API provides:
| Method | Route | Purpose |
|---|---|---|
GET |
/health |
Process health |
GET |
/ready |
SQLite readiness |
POST |
/v1/scans |
Scan a submitted normalized inventory with optional policy |
GET |
/v1/runs |
Paginated run history |
GET |
/v1/runs/{run_id} |
Complete stored report |
GET |
/v1/runs/{run_id}/diff/{baseline_run_id} |
Finding-ID diff |
GET |
/v1/runs/{run_id}/findings |
Filtered findings for one run |
GET |
/v1/runs/{run_id}/inventory |
Inventory for one run |
GET |
/v1/findings |
Cross-run finding query |
GET |
/v1/inventory |
Cross-run component query |
GET |
/v1/reports/{run_id} |
JSON or YAML by Accept negotiation |
POST |
/v1/policies/validate |
Validate a policy document |
POST |
/v1/policies/evaluate |
Evaluate a policy against a report at an explicit time |
POST |
/v1/exceptions/validate |
Validate one exception |
API requests have a configurable body limit, a 30-second processing timeout,
bounded concurrent scan capacity, validated pagination and filters, structured
error envelopes, and an x-request-id response header. CORS permits only GET
and POST with the documented request headers and does not enable arbitrary
origins.
The default bind is 127.0.0.1:8080. Binding to a non-loopback address is
rejected unless auth_bearer_sha256 is configured. Authentication compares the
SHA-256 digest of a supplied bearer token without storing or logging the raw
token. Hooray provides HTTP, not TLS termination; deploy a trusted TLS reverse
proxy when exposing the service beyond a host boundary.
The CLI generates bounded templates for pre-commit, GitHub Actions, universal GitLab CI, and GitLab 19.2+ Ultimate security ingestion:
hooray integrations generate pre-commit --output .pre-commit-config.yaml
hooray integrations generate github-actions --output hooray.yml
hooray integrations generate gitlab-ci --output hooray.gitlab-ci.yml
hooray integrations generate gitlab-security --output hooray.gitlab-security.ymlBoth GitLab templates run one scan and publish Code Quality, JUnit, and dotenv
reports before enforcing policy in a later security-gate stage. The
gitlab-security template additionally declares SARIF security ingestion and
CycloneDX SBOM ingestion for GitLab 19.2+ Ultimate. SARIF is the vulnerability,
SAST, and supported secret-detection feed; CycloneDX populates GitLab's
dependency list and continuous dependency scanning. Consumers with an existing
top-level stages list must merge the security-gate stage.
HOORAY_IMAGE is required and has no mutable fallback. Set it to the verified
ghcr.io/openhoo/hooray@sha256:<digest> recorded in the matching GitHub Release.
Before merging template support, an OpenHoo owner must bootstrap the GHCR package
from this repository's Dockerfile, link it to openhoo/hooray, make it public,
remove local credentials, and prove an anonymous digest pull, --version, and
UID 1000 execution. Keep that bootstrap image until a normal release image is
published and anonymously verified. The templates default HOORAY_INPUT to .
and HOORAY_POLICY to hooray-policy.yaml; that policy file must exist because
the scan engine always loads policy. Set HOORAY_DISABLED=true to skip both
jobs.
Review generated templates before adoption. The integration library also renders bounded GitHub SARIF and check-run payloads, GitLab Code Quality, Slack summaries, VS Code/LSP diagnostics, pull-request gates, and HTTPS-only signed webhooks. Webhook signatures are versioned, secrets must be 16–4,096 bytes, URLs cannot contain credentials, and verification uses constant-time comparison.
Hooray requires Rust 1.90 or later to build from source.
cargo install hooray --lockedFor repository development:
git clone https://github.com/openhoo/hooray.git
cd hooray
cargo build --locked --releaseThe top-level syntax is:
hooray [--config FILE] <COMMAND>
Commands and subcommands:
hooray scan project INPUT [--policy FILE] [--baseline RUN_ID] [--new-findings-only] [--format FORMAT] [--output FILE]
hooray scan sbom INPUT [--policy FILE] [--baseline RUN_ID] [--new-findings-only] [--format FORMAT] [--output FILE]
hooray scan artifact INPUT [--policy FILE] [--baseline RUN_ID] [--new-findings-only] [--format FORMAT] [--output FILE]
hooray scan container INPUT [--policy FILE] [--baseline RUN_ID] [--new-findings-only] [--format FORMAT] [--output FILE]
hooray scan auto INPUT [--policy FILE] [--baseline RUN_ID] [--new-findings-only] [--format FORMAT] [--output FILE]
hooray policy validate FILE
hooray policy evaluate FILE --run-id RUN_ID [--format json|yaml] [--output FILE]
hooray inventory [--run-id RUN_ID] [--format json|yaml] [--output FILE]
hooray history list [--limit 1..1000] [--offset N] [--format json|yaml] [--output FILE]
hooray history show RUN_ID [--format json|yaml] [--output FILE]
hooray history diff PREVIOUS_RUN_ID CURRENT_RUN_ID [--format json|yaml] [--output FILE]
hooray report RUN_ID [--format FORMAT] [--output FILE]
hooray serve
hooray monitor [--once]
hooray integrations generate pre-commit|github-actions|gitlab-ci|gitlab-security [--output FILE]
INPUT must match the selected scan subcommand. Use - as input only with
scan sbom or scan auto. Output defaults to JSON on standard output; use
--output FILE for a file. gitlab-artifacts instead requires a new directory
path. The default policy is hooray-policy.yaml and the default history database
is hooray.db.
Examples:
hooray scan project . --policy hooray-policy.yaml --format table
hooray scan sbom bom.cdx.json --format cyclonedx-vex --output result.cdx.json
cat bom.cdx.json | hooray scan sbom - --format json-lines
hooray scan artifact release.zip --format sarif --output hooray.sarif
hooray scan container image.tar --format spdx --output inventory.spdx.json
hooray scan auto ./input --format gitlab-code-quality --output gl-code-quality-report.json
hooray scan auto ./input --format gitlab-sarif --output gl-sarif-report.sarif
hooray scan auto ./input --format gitlab-cyclonedx --output gl-sbom-hooray.cdx.json
hooray scan auto ./input --format gitlab-artifacts --output .hooray-gitlabFull scan and stored-report commands support every format below. Inventory, history, and standalone policy-evaluation commands support JSON and YAML only.
| CLI value | Content |
|---|---|
json |
Canonical structured scan report |
yaml |
Canonical report serialized as YAML |
table |
Deterministic human-readable text table |
sarif |
Generic SARIF 2.1.0 |
gitlab-sarif |
GitLab 19.2+ SARIF 2.1.0 security ingestion |
junit |
JUnit XML for CI test-report ingestion |
html |
Standalone escaped HTML report |
cyclonedx-vex |
CycloneDX JSON with vulnerability analysis |
gitlab-cyclonedx |
CycloneDX 1.6 inventory for GitLab dependency ingestion |
spdx |
SPDX 2.3 JSON inventory |
gitlab-code-quality |
GitLab Code Quality JSON |
json-lines |
NDJSON envelopes for run, component, finding, policy, and summary records |
gitlab-artifacts |
Atomic directory bundle containing all five GitLab artifacts |
The gitlab-artifacts directory contains exactly
gl-code-quality-report.json, gl-sarif-report.sarif,
gl-sbom-hooray.cdx.json, gl-junit-report.xml, and hooray.env. The
destination parent must already exist and the destination itself must not.
Rendered reports validate model invariants, enforce item/text/output bounds, sort
stable collections deterministically, escape format-specific content, and
redact sensitive property names before serialization. The canonical report
format version is 1.0.0; scan reports currently use schema version 1.
Pass a YAML or TOML file with global --config FILE. Files reject unknown
fields. Environment variables with the HOORAY_ prefix override the loaded file
or defaults; unknown HOORAY_ variables are errors.
max_concurrency: 32
max_request_bytes: 1048576
max_input_bytes: 104857600
max_archive_bytes: 536870912
max_archive_entries: 100000
database_path: hooray.db
osv_url: https://api.osv.dev
osv_connect_timeout_secs: 10
osv_request_timeout_secs: 30
policy_path: hooray-policy.yaml
monitor_interval_secs: 300
api_bind: 127.0.0.1:8080
auth_bearer_sha256: null
offline: falseEnvironment names are the uppercase field names, for example
HOORAY_DATABASE_PATH, HOORAY_OSV_URL, HOORAY_API_BIND,
HOORAY_AUTH_BEARER_SHA256, and HOORAY_OFFLINE.
Security and resource defaults:
- 32 concurrent OSV/API scan slots;
- 1 MiB API request bodies;
- 100 MiB input and standard-input bound;
- 512 MiB expanded archive bound;
- 100,000 archive entries;
- configuration values of 10 seconds for OSV connect timeout and 30 seconds for OSV request timeout;
- loopback-only API binding unless bearer authentication is configured;
- no symbolic-link traversal for inventory and license collection;
- path traversal, archive links, OCI digest mismatches, malformed documents, and model-invariant violations fail closed; and
- maximum accepted configuration values are validated before execution.
The OSV URL must use HTTP or HTTPS, include a host, and must not contain embedded credentials, a query, or a fragment. Prefer the default HTTPS endpoint.
The timeout and monitor_interval_secs fields are validated configuration
values, but the current CLI OSV client uses reqwest's client defaults and the
monitor service polls every 30 seconds. Do not rely on those three fields as
active runtime overrides in this release.
| Code | Meaning |
|---|---|
0 |
Command completed and policy produced no denied decisions |
1 |
Scan, stored report, or standalone policy evaluation contained denied decisions |
2 |
Configuration, input, network, storage, validation, rendering, or other operational error |
Warnings do not produce exit code 1. Policy controls the gate; there is no
legacy severity-only --fail-on interface.
| Ecosystem | Recognized files | Inventory behavior |
|---|---|---|
| Rust | Cargo.lock, optional sibling Cargo.toml |
Packages, checksums, direct dependency hints, Cargo purls |
| npm | package-lock.json |
Package graph, dev/optional scope, npm purls |
| Python | requirements.txt |
Pinned name==version requirements, PyPI purls |
| Go | go.sum |
Module/version entries, Go purls |
| NuGet | packages.lock.json |
Framework dependency graph, direct/transitive hints, NuGet purls |
| CycloneDX | JSON SBOM with versioned purls | Nested and declared dependency edges, scope, provenance |
| OCI/Docker | OCI layout or OCI/Docker TAR | Layer application with whiteouts, digest validation, supported lockfiles from final filesystem |
| Generic artifact | .zip or .tar |
Supported lockfiles discovered in the bounded archive |
Project-directory detection requires at least one supported lockfile. Files with similar names are not treated as supported inputs, and malformed supported files fail rather than being silently skipped.
The repository CI runs the following commands:
cargo fmt --check
cargo clippy --locked --all-targets --all-features -- -D warnings
cargo test --locked --all-targets --all-features
cargo llvm-cov --locked --all-targets --all-features --fail-under-lines 90
cargo deny check advisories bans licenses sourcesThe implemented test suite currently contains 234 tests and the current measured
line coverage is 93.52%. CI enforces a 90% line-coverage floor. Commit messages
are linted as Conventional Commits, and dependency advisories, bans, licenses,
and sources are checked on every pull request and push to main.
Hooversion derives releases from Conventional Commits on main, updates the
manifest, lockfile, and changelog, creates the release commit and v<version>
tag, and publishes a GitHub Release. The release workflow attaches the optimized
Linux x86_64 binary, archive, and checksums.
Hooray is licensed under the MIT License.