Omni Audit is a fast, zero-config CLI that scans your project for dependency confusion, typosquatting, known malicious packages, shadow registry risk, and risky registry settings. It discovers NPM, Composer, PyPI, Go, Cargo, RubyGems, Maven, Conan, and Docker Hub manifests, checks whether each declared name exists on the public registry, and reports names that are still unclaimed. It compares names to an embedded popular-package corpus for 1–2-edit near-misses, checks names and locked versions against the OpenSSF malicious-packages database, audits lockfile resolution URLs that pull from public registries instead of your internal proxy, and audits package-manager config files (.npmrc, pip.conf, composer.json, Gemfile, Dockerfiles, CI pipelines, …) for settings that let a public package replace an internal one.
Distributed as a standalone Go binary. No Node or PHP runtime required.
The problem
If your team uses private packages alongside public registries (npmjs.com, Packagist, PyPI, crates.io, Maven Central, and others), a build can resolve a malicious public package that reuses an internal name. Omni Audit flags unclaimed public names before they are hijacked.
Attackers also publish packages whose names look like popular libraries (react-domm, crossenv). A single typo in a manifest can install malware. Omni Audit flags those near-misses offline against a high-download corpus.
Some packages need no typo at all: they were published as malware outright, or a legitimate package (nx, chalk, axios) shipped malicious releases from a hijacked maintainer account. Omni Audit checks every declared name and locked version against the 239k OpenSSF malicious-packages (MAL-) advisories, offline, using the database from mal-check.
Separately, developers often bypass the company registry proxy — a misconfigured .npmrc, or a lockfile copied from outside the org — so installs hit registry.npmjs.org / pypi.org directly and skip your security perimeter. Omni Audit flags those lockfile resolutions too.
Often the cause is one line of configuration. pip install --extra-index-url asks every index and installs the highest version it finds, so a public upload of your internal name with version 99.0 wins; that is how the 2022 torchtriton compromise reached PyTorch nightly users. Composer repositories marked "canonical": false, a Gemfile with two global source lines, and uv's unsafe-best-match index strategy open the same door. Omni Audit flags those settings, plus registry tokens committed in plain text and registries reached over plain HTTP, offline.
Auditing is the first step. Omni Line is the durable fix: a self-hosted registry with Virtual Registries that route internal and external packages through one URL.
Install
Prebuilt binaries
Download the latest release from GitHub Releases. See SECURITY.md for cosign, attestation, and checksum verification commands.
From source
Requires Go 1.20+:
go install github.com/omni-line/omni-audit/cmd/omni-audit@latest
Or clone and build:
git clone https://github.com/omni-line/omni-audit.git
cd omni-audit
make build
./bin/omni-audit --helpWith an AI agent
Paste this into Claude Code, Codex, or Cursor at the repository root:
Read https://raw.githubusercontent.com/omni-line/omni-audit/main/skills/omni-audit/SKILL.md and follow it to set up Omni Audit for this repository.
The agent installs a pinned, checksum-verified release, runs a first scan, proposes flags from what it finds in the repo, and drafts a CI step for you to approve.
Quick start
# Scan the current directory omni-audit # Scan a path omni-audit ./apps/api # Scan a single manifest omni-audit ./services/billing/requirements.txt # JSON for CI omni-audit --format json --no-marketing # SARIF for GitHub code scanning / security dashboards omni-audit --format sarif > omni-audit.sarif # Skip names you own and fixture directories omni-audit --safe-namespace '@acme/*,acme/*' --exclude testdata,fixtures
Example text output (colors when the terminal supports them):
omni-audit v0.7.0 — Dependency audit
Backed by Omni Line — one registry for every package your team ships
✗ 2 unclaimed package names found
✗ 1 suspected typosquat found
✗ Audit failed: 14 dependencies resolving outside your expected registry proxy
SEVERITY ECOSYSTEM PACKAGE VERSION LOCATION SECTION REASON
critical composer acme/internal-sdk ^1.0 composer.json:7 require unclaimed
critical npm @acme/internal-utils 1.0.0 package.json:5 dependencies unclaimed
critical npm crossenv 7.0.3 package.json:12 dependencies typosquat
high npm lodash 4.17.21 package-lock.json:842 registry.npmjs.org shadow_registry
Anyone can publish these names on the public registry. If a build resolves
them there instead of your private source, it installs the publisher's code.
These names are 1–2 edits away from popular packages. A single typo in a
manifest can install malware that steals env vars and CI tokens.
These installs bypass your internal registry proxy and its security controls.
Lockfiles that embed public registry URLs pull directly from the internet.
How to fix
composer Register the vendor name on Packagist so nobody else can publish under it, ...
npm Claim the name (or its @scope as an npm organization) on npmjs.com, ...
typosquat Confirm the package name is intentional. If it is a typo, correct it ...
sources Point the package manager at your internal registry proxy and regenerate the lockfile ...
Scanned 2 manifests · 1 lockfile · 8 packages · 14 resolved deps · 17 findings · 0 skipped · 0 errors in 412ms
───
Unclaimed names, typosquats, and lockfiles that bypass your proxy are supply-chain gaps.
Omni Line Virtual Registries give you one URL that routes internal and
external packages correctly — so developers cannot misconfigure the source.
One UI, one API, your infrastructure.
https://omniline.app · docs: https://omniline.app/docs
How it works
- Walks the tree for supported manifests (skips
node_modules,vendor,target,.bundle,.venv,venv,__pycache__,.git,dist,build, and other dependency/cache dirs, plus anything matched by--exclude) - Collects declared dependencies with their section and line number:
- npm:
package.jsondependency sections; local/VCS specs skipped; aliases checked under the real name - Composer:
composer.jsonrequire/require-dev(platform packages skipped) - PyPI:
requirements*.txt,requirements/*.txt,pyproject.toml(PEP 621 / PEP 735) - Go:
go.modrequirepaths viaproxy.golang.org - Cargo:
Cargo.tomldependency tables (path/git/workspace skipped) via crates.io - RubyGems:
Gemfile/*.gemspecvia rubygems.org - Maven:
pom.xmlgroupId:artifactIdvia Maven Central - Conan:
conanfile.txtrequires via ConanCenter - Docker:
DockerfileFROMand Composeimage:refs via Docker Hub (unqualified /docker.ioonly)
- npm:
- Checks each distinct name once per ecosystem against its public registry (lightweight
HEAD/ search where needed), with retries and backoff - For npm scopes and Packagist vendors, also checks whether the namespace itself is claimed, and assigns severity (
critical/high/low) - Reports names that return 404 as
reason=unclaimed; checks that fail are warnings, never treated as clean - Compares each declared name to an embedded popular-package corpus (and to namespace peers) and reports 1–2-edit near-misses as
reason=typosquat— offline, no network - Walks supported lockfiles (
package-lock.json,npm-shrinkwrap.json,yarn.lock,poetry.lock,composer.lock), extracts resolution URLs, and flags known public registry hosts (or any host outside--expected-host) asreason=shadow_registry— offline, no network - Audits package-manager configuration files and reports risky settings as
reason=registry_config, with the risk inkind(offline, no network; table below) - Checks every lockfile resolution and every declared dependency against an embedded snapshot of OpenSSF malicious-packages (
MAL-advisories, via mal-check'spkg/maldb) and reports matches asreason=malicious:criticalwhen the locked version (or every version) is malware,highwhen only some versions are and the declaration is not pinned. Lockfile versions win over manifest ranges; clean versions of once-hijacked packages are not reported — offline, no network
Registry configuration checks
kind |
Severity | Flagged in |
|---|---|---|
extra_index |
high | --extra-index-url / extra-index-url / PIP_EXTRA_INDEX_URL in pip.conf, pip.ini, requirements and constraints files, Dockerfiles, CI pipelines (.github/workflows, .gitlab-ci.yml, CircleCI, Bitbucket, Azure, Jenkinsfile), Makefile, *.sh; install_search_all_sources = true in a Pipfile. uv pip install --extra-index-url is not flagged: uv's default first-index strategy is not affected |
unsafe_index_strategy |
high | uv index-strategy = "unsafe-…" in pyproject.toml / uv.toml, --index-strategy unsafe-…, UV_INDEX_STRATEGY=unsafe-… |
non_canonical_repository |
high | composer.json repositories with "canonical": false |
multiple_global_sources |
high | a Gemfile / gems.rb with more than one global source (block sources are fine) |
plaintext_credential |
high | literal tokens and passwords in .npmrc, .yarnrc.yml, Composer auth.json, .cargo/config.toml / credentials.toml, Maven settings.xml, and registry URLs (https://user:pass@…). ${VAR} references, Maven-encrypted {…} passwords, and files excluded by a .gitignore are not flagged |
insecure_transport |
medium | plain-HTTP registry URLs (loopback hosts excepted), strict-ssl=false, enableStrictSsl: false, unsafeHttpWhitelist, --trusted-host, verify_ssl = false, "secure-http": false |
Secret values are never printed or written to JSON/SARIF; URLs are redacted (https://deploy:xxxxx@…). Go has no file-level equivalent (GOPRIVATE lives in the environment), so it is not covered.
Security properties
- Only package names are sent, and only to the public registries above (HTTPS, TLS 1.2+, no HTTPS→HTTP redirects). Names that are not valid for the registry are never sent. Lockfile source auditing, typosquat, malicious-package, and registry configuration checks are offline (no network).
- Manifests and lockfiles are read with a 10 MiB cap; FIFOs/devices and symlinks that resolve outside the scan root are skipped.
- Values from scanned files are escaped before being printed, so a crafted manifest cannot inject terminal escape sequences.
HTTPS_PROXY/NO_PROXYare honored for locked-down networks.
Exit codes
| Code | Meaning |
|---|---|
0 |
No findings (or --fail-on none) |
1 |
One or more findings at or above --min-severity (--fail-on any, default) |
2 |
Usage or runtime error, or an incomplete scan with --strict |
Without --strict, registry failures and unreadable manifests are reported as warnings and do not change the exit code. Use --strict in CI when an unverified package should block the pipeline. Use --min-severity high (or critical) to ignore low-severity scoped findings when the scope/vendor is already claimed.
Flags
| Flag | Description |
|---|---|
--format text|json|sarif |
Output format (default text) |
--safe-namespace |
Globs for namespaces you own; matches are skipped (repeatable / comma-separated) |
--ignore |
Skip package name globs |
--allow |
Exact package names treated as safe for typosquat checks |
--expected-host |
Internal registry hostname or URL; lockfile resolutions outside these hosts are findings (repeatable / comma-separated). Without this flag, only known public registry hosts are flagged. |
--exclude |
Skip paths: directory/file names or globs relative to the scan root |
--only |
Run only these checks: unclaimed, shadow_registry, typosquat, malicious, registry_config (repeatable / comma-separated; the same names as reason). --only malicious,typosquat,registry_config makes no network requests |
--no-unclaimed |
Disable the public-registry checks for unclaimed names (the only check that uses the network) |
--no-sources |
Disable the lockfile audit for resolutions outside the expected registry hosts (lockfiles are still read for the malicious-package check) |
--no-typosquat |
Disable offline typosquat checks |
--no-malicious |
Disable the offline known-malicious-package check |
--no-registry-config |
Disable the offline audit of package-manager config files |
--distance |
Maximum edit distance for typosquat matches (1 or 2, default 2) |
--scope |
Namespace to peer-check for typosquats (e.g. @acme; repeatable) |
--no-scope-peers |
Disable automatic namespace peer typosquat checks |
--strict |
Exit 2 if any manifest or package could not be verified |
--timeout |
Per-request timeout (default 10s) |
--retries |
Retries for 429 / 5xx / network errors (default 2, max 10) |
--concurrency |
Parallel checks (default 16, max 256) |
--fail-on any|none |
Whether findings fail the process |
--min-severity low|medium|high|critical |
Minimum severity that exits 1 (default low) |
-q / --quiet |
Findings table only; no banner, warnings, summary, or marketing |
--no-marketing |
Hide Omni Line CTA / JSON sponsor |
--marketing |
Force marketing even when non-TTY |
--color auto|always|never |
ANSI colors (default auto on TTY) |
-v / --verbose |
Show package URLs and all warnings |
--version |
Print version |
JSON output
{
"schema_version": 1,
"version": "0.3.0",
"complete": true,
"findings": [
{
"ecosystem": "npm",
"package": "@acme/internal-utils",
"version": "1.0.0",
"manifest": "package.json",
"line": 5,
"group": "dependencies",
"reason": "unclaimed",
"severity": "critical",
"namespace": "acme",
"namespace_status": "unclaimed",
"registry": "registry.npmjs.org",
"url": "https://www.npmjs.com/package/@acme/internal-utils",
"remediation": "Claim the name (or its @scope as an npm organization) ..."
}
],
"stats": { "manifests": 1, "packages": 4, "unique_packages": 4, "findings": 1, "skipped": 0, "errors": 0, "duration_ms": 412 }
}complete is false when any warning was raised (see warnings[], each with a kind of walk, manifest, registry, namespace, lockfile, or config). New fields may be added; breaking changes bump schema_version.
Environment:
OMNI_AUDIT_NO_MARKETING=1— same as--no-marketing(handy in CI)NO_COLOR=1— disable colors (no-color.org)FORCE_COLOR=1— enable colors when not a TTY
JSON includes an optional top-level sponsor object by default. Use --no-marketing or -q for a silent machine payload.
GitHub Action
- uses: actions/checkout@v4 - uses: omni-line/omni-audit@v0.10.0 with: args: --safe-namespace '@your-org/*' --strict
The action downloads the release binary for the runner (Linux, macOS, or
Windows; x64 or arm64), verifies it against checksums.txt, and runs it. It
fails the step on findings, like the CLI.
| Input | Default | Description |
|---|---|---|
path |
. |
Directory or manifest to scan |
args |
Extra flags, parsed like a shell command line | |
version |
the action's tag | Release to run; the latest release when the action is used at a branch or SHA |
sarif |
false |
Write SARIF to sarif-file instead of text to the log |
sarif-file |
omni-audit.sarif |
SARIF output path |
Outputs: exit-code and sarif-file. For GitHub code scanning:
- uses: omni-line/omni-audit@v0.10.0 with: sarif: true args: --fail-on none - uses: github/codeql-action/upload-sarif@v3 with: sarif_file: omni-audit.sarif
CI example
- name: Dependency confusion audit run: | curl -sL https://github.com/omni-line/omni-audit/releases/latest/download/omni-audit_Linux_x86_64.tar.gz | tar xz ./omni-audit --format json --no-marketing --strict --safe-namespace '@your-org/*'
GitHub code scanning (findings appear as alerts with file/line annotations):
- name: Dependency confusion audit run: ./omni-audit --format sarif --fail-on none > omni-audit.sarif - uses: github/codeql-action/upload-sarif@v3 with: sarif_file: omni-audit.sarif
Run from the repository root so SARIF paths are repository-relative.
Roadmap
- Scope-aware
.npmrcpolicy (internal@scopewithout a@scope:registrymapping) - Optional Omni Line registry URL to verify private existence
- Poetry/
Pipfiletable-style dependency maps (requirements + PEP 621 covered today) - Gradle manifests,
conanfile.py, and non-Hub OCI registries (GHCR, Quay, ECR) - Malicious-package checks for
pnpm-lock.yaml,Gemfile.lock,Cargo.lock, and NuGet lockfiles (use mal-check for those today)
Contributing
See CONTRIBUTING.md, MAINTAINERS.md, and CODE_OF_CONDUCT.md. Security reports: SECURITY.md.
License
MIT · Copyright Omni Line and contributors · See NOTICE. The embedded malicious-package database is derived from OpenSSF malicious-packages, Apache-2.0.
Omni Audit is built and maintained by Omni Line — one self-hosted registry for every package your team ships.