GuardDog scores package risks by correlating capabilities with threat indicators
:snake: :mag: GuardDog is a CLI tool to Identify malicious PyPI and npm packages
At a glance
- What is it?
- GuardDog is a DataDog CLI that scans PyPI, npm, Go, Rust, RubyGems, GitHub Actions and VSCode extensions with YARA rules and metadata checks, then scores findings as attack chains. It suits supply chain review and dependency triage, and it is a scanner rather than a sandbox of record.
- Who is it for?
- Adopt GuardDog if you need to triage packages from several registries with a scanner that ranks findings by attack chain rather than listing patterns, and if you can run its YARA rules and metadata checks against a local copy of the package. Do not treat its score as a verdict, because the README says a high score means near-certainty of compromise only for a multi-stage chain with source code evidence, and a zero score means no risks detected rather than no risk.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 4 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Correlation, not pattern counting, is the design difference
GuardDog is a CLI tool to identify malicious PyPI and npm packages, and by extension Go modules, Rust crates, RubyGems, GitHub actions and VSCode extensions. It runs static analysis on package source code through YARA rules and analyzes package metadata to detect supply chain attacks.
The README states what makes it different in one line: instead of just listing suspicious patterns, GuardDog correlates findings to identify actual risks based on attack chains. A package needs both the capability to perform an action, such as network access, and a threat indicator, such as a suspicious domain, in the same file to be flagged as high risk.
The reasoning behind that choice is stated in a section called Why This Approach, and it is the most defensible part of the design. Traditional SAST tools flag every suspicious pattern independently, which leads to alert fatigue. Capability alone is not malicious, since network libraries should make HTTP requests. Threat indicators alone might be false positives, such as test fixtures or documentation. Capability plus threat together indicates actual risk, which the README describes as code that can and will do something malicious.
For a package registry, that distinction is the whole game. A package that imports requests and contains a suspicious URL in a test fixture is not the same finding as a package that both opens a socket and posts to an unfamiliar host, and a scanner that treats them identically gets ignored within a week.
The project is a DataDog project, licensed Apache-2.0, with the NOTICE and LICENSE-3rdparty.csv files in the repository root and a SECURITY.md for reporting issues.
The four step pipeline runs from detection through correlation to a 0-10 score
The README describes a risk-based detection model with four stages.
Detection is where rules identify either capabilities, meaning what code can do, or threats, meaning suspicious indicators. Correlation is where capabilities and threats found in the same file form risks, and cross-file matches also form risks but with downgraded severity. Scoring is where risks are scored from 0 to 10 based on attack chain completeness and sophistication. Reporting is where packages receive a severity rating of low, medium or high with a detailed risk breakdown.
The score itself is a weighted combination of four factors: severity at 30 percent, which is the highest severity finding; attack chain at 20 percent, based on the presence of complete attack stages; specificity at 30 percent, how specific the patterns are to malware rather than legitimate code; and sophistication at 20 percent, the technique advancement level.
The score labels give the reading. Zero means no risks detected. 0.1 to 3 is low risk, covering single-stage threats and low specificity. 3.1 to 7.5 is medium risk, covering a partial attack chain, metadata indicators, or single-stage code findings. 7.6 to 10 is high risk, which the README describes as a multi-stage attack chain with source code evidence, and near-certainty of compromise.
That last phrase is the one to hold on to. A score of 8 means the tool has found a chain, not that a package has been proven malicious, and the 7.6 threshold is set high on purpose.
Attack chain stages are mapped onto MITRE ATT&CK categories
The attack chain concept is not a GuardDog invention, and the README anchors it in MITRE ATT&CK. The stages are Early, covering initial access and execution capabilities; Mid, covering persistence, defense evasion and credential access; and Late, covering command and control, exfiltration and impact.
Three things follow from using that taxonomy. First, the score is not just a count of findings, it reflects whether the stages are sequential and complete, which is why a package with several mid-stage indicators can still score lower than one with an early, mid and late chain. Second, a mid or late stage finding is evidence of intent, because a package that only opens a socket is doing something every HTTP client does, while a package that reads credentials and posts them somewhere is not. Third, the mapping gives you a shared vocabulary with other security tooling, which matters if the finding has to go into a ticket that a security team will read.
The chain is also why the cross-file rule downgrades severity. A capability in one module and a threat indicator in another is weaker evidence than both in the same file, and the model reflects that rather than flattening it.
One limit is worth naming. The stages are derived from rule matches, so a package using an obfuscated or novel technique does not score higher, it scores lower, because the rules did not match. That is inherent to a rule-based approach and applies to any YARA-driven scanner, but it is the reason the score should be read as a ranking rather than a verdict.
Seven package sources, one command shape, and a verify mode for lockfiles
GuardDog covers an unusual breadth of ecosystems, and the README lists what it downloads and scans for each. NPM packages hosted on npmjs.org, PyPI source tarballs from PyPI.org, Go source files from repositories on GitHub, Rust crates from crates.io, RubyGems packages from rubygems.org, GitHub Actions implemented in JavaScript from repositories on GitHub, and VSCode Extensions as .vsix packages from marketplace.visualstudio.com.
The command surface is uniform once you know the shape. A scan of a named package:
# Scan the most recent version of the 'requests' package
guarddog pypi scan requests
# Scan a specific version of the 'requests' package
guarddog pypi scan requests --version 2.28.1A scan of a local artifact, which is what you want when triaging a package that has already been downloaded:
# Scan a local package archive
guarddog pypi scan /tmp/triage.tar.gz
# Scan a local package directory
guarddog pypi scan /tmp/triage/There is also an S3 form, where a folder prefix or a single archive object can be scanned directly, and the same commands work across the other ecosystems:
guarddog npm scan express
guarddog go scan github.com/DataDog/dd-trace-go
guarddog crates scan serde
guarddog rubygems scan rails
guarddog github_action scan DataDog/synthetics-ci-github-action
guarddog extension scan ms-python.pythonThe verify mode is the one to build into CI, because it takes a lockfile or requirements file and scans every package referenced in it:
# Scan every package referenced in a requirements.txt file of a local folder
guarddog pypi verify workspace/guarddog/requirements.txt
# Scan every package referenced in a requirements.txt file and output a sarif file
guarddog pypi verify --output-format=sarif workspace/guarddog/requirements.txtThe same pattern exists for go.mod, Cargo.lock, Gemfile.lock and GitHub workflow files. The SARIF output format is the one to pick if your CI already consumes security findings in that format, and JSON output works for every command.
Rule selection, exclusions and debug mode shape a scan run
The flags that matter are the ones that control which rules run. You can restrict a scan to specific heuristics:
# Scan the 'request' package using 2 specific heuristics
guarddog pypi scan requests --rules exec-base64 --rules code-execution
# Scan the 'requests' package using all rules but one
guarddog pypi scan requests --exclude-rules exec-base64That exclusion flag is the practical one for a team with a false positive backlog, since it lets you keep a rule active everywhere except where you have already established it misfires. Restricting to two rules, by contrast, is useful when you are reproducing a specific finding.
The log level is the third control, and for a security tool it is the one that determines how much you can investigate a result:
# Run in debug mode
guarddog --log-level debug npm scan expressThe rules themselves are documented as generated content. The repository has a RULES.md at the root and a WRITING_RULES.md, and the Makefile has a docs target that regenerates the rules documentation with python scripts/generate-rules-docs.py RULES.md. That means the rule catalogue is a build artefact rather than hand-maintained prose, which is what you want in a tool where the rule set changes often.
There is also an update-top-pkg-list target that runs the typosquatting analysis modules for npm and PyPI, which tells you the popular-package list used for name similarity checks is refreshed by a script rather than committed by hand.
Scanning runs in a kernel sandbox, and the docs cover the rules engine
The README states that when scanning packages, GuardDog runs source code analysis inside a kernel-level sandbox, using Landlock on Linux and Seatbelt on macOS, through the nono-py package. The distinction from a container is worth noting, because a kernel-level sandbox restricts what the analysed code can do to the host kernel rather than to a container runtime's weaker boundary.
That matters for a tool whose job is to open and inspect untrusted archives from a public registry. The analysis is static, but the extraction and inspection path still executes helper code, and the sandbox is what keeps a hostile package from doing anything beyond being read.
The rule engine documentation is unusually well organised for a security tool, with the RULES.md catalogue, a WRITING_RULES.md guide, an evals/ directory for the evaluation tooling, and a notebooks/ directory. The pyproject lists python-tlsh among the dev dependencies, with a comment saying it is used by the eval clustering tooling in evals/cluster.py for fuzzy fingerprint matching, which is a good sign: a rule-based scanner with a documented evaluation set is more trustworthy than one without.
The evals and the clustering tooling exist because rules produce findings that need to be compared across packages, for grouping related malicious packages by shared code patterns. That is a different kind of tooling from the scanner itself, and it is the part of the repository that helps you understand what the rules actually catch.
Apache-2.0, a v3 rewrite, and release notes that ship with the tool
The licence is Apache-2.0, and the LICENSE and NOTICE files in the repository root are the authoritative text, with third-party licences tracked in LICENSE-3rdparty.csv. This is a licensing statement rather than legal advice.
The default branch is v3, which tells you the project went through a major rewrite. Recent releases are v3.2.0 on 2026-08-12, v3.1.0 on 2026-07-08 and v3.0.2 on 2026-06-26, and the last push was on 2026-10-19, so the v3 line is moving.
The packaging reflects a modern Python project. The pyproject requires Python 3.10 or newer, uses poetry-core as the build backend, and pins the version at 3.2.0 under the poetry section. The Dockerfile builds from a python:3.10-slim-bullseye base image with a pinned digest, creates a non-root user with uid 1000, copies the built dependencies into the runtime stage, and sets the entrypoint to guarddog. The non-root user in the image is a sensible default for a tool that handles untrusted archives.
There is a solver setting min-release-age=7, which tells the dependency resolver to prefer package versions at least seven days old, a supply chain hygiene measure on the tool's own dependencies. The Makefile separates the test suite into YARA rules, metadata rules, core and reporters, each with its own coverage file, then combines them for a report. There is also a type-check target running mypy over the guarddog package and a lint target using flake8 with a max line length of 120.
For context, the project links a Datadog Agent integration and a Cloud SIEM content pack, so findings can flow into existing security workflows rather than only into a terminal.
Editorial conclusion
Adopt GuardDog if you need to triage packages from several registries with a scanner that ranks findings by attack chain rather than listing patterns, and if you can run its YARA rules and metadata checks against a local copy of the package. Do not treat its score as a verdict, because the README says a high score means near-certainty of compromise only for a multi-stage chain with source code evidence, and a zero score means no risks detected rather than no risk. Before you deploy it, decide whether the kernel-level sandbox is available on your platform, since scanning runs source code analysis inside Landlock on Linux or Seatbelt on macOS through nono-py, and pin the version because the rules change between releases.
Frequently asked questions
How do I install and run GuardDog?
The easiest way is uvx guarddog pypi scan requests. For a local install use uv tool install guarddog or pip install guarddog, or pull the image with docker pull ghcr.io/datadog/guarddog and alias it to docker run --rm ghcr.io/datadog/guarddog. On Windows the only supported installation method is Docker.
What is the difference between GuardDog scan and verify?
The scan command takes a package name, a version, a local archive or directory, or an S3 path. The verify command takes a lockfile or requirements file and scans every package referenced in it, for example guarddog pypi verify workspace/guarddog/requirements.txt, which is the form intended for CI. Only verify supports the sarif output format.
How is the GuardDog risk score calculated?
Risks are scored 0 to 10 from four weighted factors: severity at 30 percent, attack chain at 20 percent, specificity at 30 percent and sophistication at 20 percent. Scores from 7.6 to 10 mean a multi-stage attack chain with source code evidence, 3.1 to 7.5 is medium risk, 0.1 to 3 is low risk, and 0 means no risks detected.
Which package ecosystems does GuardDog scan?
The README lists npm packages from npmjs.org, PyPI source tarballs, Go source files from GitHub repositories, Rust crates from crates.io, RubyGems packages, GitHub Actions implemented in JavaScript, and VSCode extensions as .vsix packages from the Visual Studio marketplace.
Does GuardDog run the code of the package it scans?
The README states that GuardDog runs source code analysis inside a kernel-level sandbox, Landlock on Linux and Seatbelt on macOS, using the nono-py package. The analysis itself is static, carried out with YARA rules plus metadata checks.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/datadog-guarddog)Was this useful?