# packj: the table says eight ecosystems, the contents list says five

> ossillate-inc/packj vets packages from open-source registries for the attributes that make supply chain attacks work, auditing statically by default and installing under strace when asked, with a browserify walkthrough that marks a 14,000 star package undesirable, packaging that builds a C sandbox and imports a module Python has since removed, and a last tagged release from February 2023.

**ossillate-inc/packj** — Packj stops :zap: Solarwinds-, ESLint-, and PyTorch-like attacks by flagging malicious/vulnerable open-source dependencies ("weak links") in your software supply-chain

- Repository: https://github.com/ossillate-inc/packj
- Website: https://packj.dev
- Stars: 694 · Forks: 38
- Language: Python
- License: AGPL-3.0
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/ossillate-inc-packj

## Three different answers to which registries are supported

The documentation does not agree with itself on this, and the disagreement is worth resolving before you pick a package manager. The contents list names five: NPM, PyPI, Rubygems, PHP, Rust. The prose in that same section says Packj can vet NPM, PyPI, Rust, PHP, and RubyGems, and then adds that Rust and PHP support is work in progress. The table then marks eight with a check, adding NuGet for .NET and Maven for Java on top of Cargo and Packagist, and marks only Docker and CocoaPods unsupported. So a registry that the sentence calls work in progress carries a checkmark in the table, two registries appear in the table but not in the contents line, and the two exclusions are the container formats rather than the language ones. Locally, unpublished NPM and PyPI packages can also be vetted, which is the case most teams have not thought to ask about.

## Audit is static unless you ask for strace

The audit command comes in two shapes, and the difference between them is the difference between reading a package and running it:

```bash
python3 main.py audit -p pypi:requests rubygems:overcommit
python3 main.py audit -f npm:package.json pypi:requirements.txt
```

Naming packages directly uses `-p`, and pointing at dependency files uses `-f`. By default the audit performs static code analysis only. Passing `-t` or `--trace` switches on dynamic analysis, which installs every requested package under strace and monitors install-time behaviour. The documentation contains a typo here, writing that you can paas the flag rather than pass it, which is a small reminder that this page has not been read recently. The pairing matters for interpretation: static analysis answers what the code contains, and the trace answers what it did while installing, which is the question a supply chain attack turns on.

## The browserify run marks a popular package undesirable

The worked example is the most useful thing in the documentation, because it calibrates the tool rather than selling it. The command runs a traced audit against `npm:browserify`, and the output walks a checklist that fetches version 17.0.0, reads the description, counts 484 versions, notes 702 days since the last version, 68 days since the last release, an author email on an expired domain, a 26,838 byte readme, 2M weekly downloads, a repository with 14189 stars and 1244 forks that is original rather than forked, 2290 commits from 207 contributors, no CVEs found, and 48 dependencies. Static analysis then reports the package needs three filesystem permissions, decode, codegen, and file. The trace line counts five processes, 1130 files, and 22 network syscalls. The verdict is `5 risk(s) found, package is undesirable!`, and a JSON report is written under `/tmp`. Read that next to the star count and you understand what the tool is measuring.

## requirements.txt pins everything and the package metadata pins nothing

The dependency file is exactly pinned, with twenty-one lines including `django==4.1.1`, `requests==2.25.0`, `networkx==2.5.1`, `dnspython==2.2.1`, `tldextract==3.1.2`, `pyIsEmail==1.4.0`, `esprima==4.0.1`, `func_timeout==4.3.5`, and `protobuf==3.19.4`. Those names map onto what the checks do: DNS resolution and top-level-domain extraction with an email validator explain the expired-author-domain result, an esprima parser with a graph library and a timeout wrapper explain the static analysis, and `rarfile` is how the archive format published by RubyGems gets read. Then there is the packaging detail that undercuts all of it. `setup.py` builds its requirement list by splitting each line on `==` and keeping the first part, so `install_requires` carries the package names with no version constraints at all. The same file also lists the magic library twice, once as `python-magic` and once as `python_magic`.

## setup.py compiles a sandbox and refuses to install anywhere but Linux

The sandbox is not a container and not a virtualenv. A custom install command calls a `setup_sandbox` function that raises an exception reading Only Linux is supported on any other platform, then shells out to run `./install.sh && make` inside `packj/sandbox`, and moves the resulting shared object and a copy of `strace` into the build directory. So installing the Python package requires a compiler, the sandbox sources, and Linux, and the failure mode on a Mac or Windows is an exception rather than a degraded install. The same file copies `.packj.yaml` from the repository root into the user's home directory before installing and removes it afterwards, using bare `except` clauses that swallow everything including the case where no file was there to begin with. It also imports `distutils` in four places, including a duplicated import of the setup error type, and `distutils` was removed from the standard library in Python 3.12.

## The image carries a compiler, Node 16, and three pinned gems

The container starts from `ubuntu:22.04`, creates a local user called `ubuntu` at uid and gid 1001, and then removes Docker's apt cache cleaner so subsequent builds can reuse downloaded packages. It fetches the NodeSource setup script for Node 16 over HTTPS and executes it to add that repository, then installs a long apt list that includes `gcc`, `python3-dev`, `build-essential`, `ruby-full`, `rubygems-integration`, `protobuf-compiler`, `libmagic-dev`, `strace`, `autoconf`, and `nodejs`. Three Ruby gems are installed at exact versions: `parser:3.0.0.0`, `google-protobuf:3.21.2`, and `rubocop:1.31.1`. The Ruby side exists because Packj parses RubyGems packages, which is why a from-source install needs both `bundle install` and `pip3 install`. The sandbox shared object is then compiled during the build and moved into place, and the working directory is given to the unprivileged user with `.local`, `ruby`, and `.npm` cache directories prepared.

## Every release tag is a beta and the newest is from 2023

The release history is `v0.13-beta` on 2022-12-08, `v0.14-beta` on 2023-01-26, and `v0.15-beta` on 2023-02-01, so three tags in two months and nothing since. The last push to `main` is dated 2026-09-17, which means the branch has moved a long way past the newest tag and the version you would install from a release is not the version at the tip. The GitHub Action that runs the audit in pull requests is separately pinned at `ossillate-inc/packj-github-action@v0.0.10-beta`, a different version line from the main package. And the note at the top of the README announces a self-hosted Packj webserver and several integrations as coming later this month, with no date attached, which is the kind of line that goes stale silently. What the project has demonstrably done is report over seventy malicious PyPI and RubyGems packages, and present at PyCon, OpenSourceSummit, and BlackHat.

## Conclusion

packj does something most dependency scanners deliberately do not, which is look at what a package does when you install it rather than only at what is already in a CVE database, and the browserify walkthrough is the best argument for it in the repository: a package with fourteen thousand stars, four hundred and eighty four releases, no known vulnerabilities and two million weekly downloads still comes back with five risks. Two things to weigh before you adopt it. Every release tag is a beta and the newest is from February 2023, so the code your CI runs is not something the release history describes. And the packaging imports `distutils` and builds a C sandbox at install time, which narrows both the Python versions and the platforms it will install on. Treat it as an audit signal to read rather than a gate to enforce until you have run it against your own dependency set with the noise turned down to match your threat model.

## FAQ

### What does Packj do?

It vets packages from open-source registries for risky attributes that make them vulnerable to supply chain attacks, such as expired author email domains, long gaps between releases, and sensitive access permissions. It can detect malicious, vulnerable, abandoned, and typo-squatting packages, and a config file lets you turn individual checks off to match your threat model.

### How do I run Packj?

The Docker route is the recommended one, `docker run -v /tmp:/tmp/packj -it ossillate/packj:latest --help`, with Podman supported for isolated runs. From source it is `git clone`, then `bundle install && pip3 install -r requirements.txt`, then `python3 main.py --help`, so both Ruby and Python are needed and the install compiles a sandbox.

### How do I use Packj in GitHub Actions?

Use the marketplace action `ossillate-inc/packj-github-action@v0.0.10-beta`, set `DEPENDENCY_FILES` to the manifests you want audited such as `pypi:requirements.txt,npm:package.json,rubygems:Gemfile`, and pass `REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}` so it can report on pull requests.

### Does Packj need dynamic analysis to audit a package?

No. The `audit` command performs static code analysis by default. Passing `-t` or `--trace` installs the requested packages under strace and monitors install-time behaviour, which is how the trace line reporting process counts, file counts, and network syscalls is produced.

## Sources

- [License: AGPL-3.0](https://github.com/ossillate-inc/packj/blob/main/LICENSE)
- [ossillate-inc/packj on GitHub](https://github.com/ossillate-inc/packj)
- [Project website](https://packj.dev)
- [README](https://github.com/ossillate-inc/packj/blob/main/README.md)
- [Releases](https://github.com/ossillate-inc/packj/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/ossillate-inc-packj
