Skip to main content
Glama

devguard-mcp

CI python license

A read-only command-line tool and MCP server that detects two developer-targeting supply-chain campaigns:

  • PolinRider: a DPRK-linked implant that hides obfuscated JavaScript in the config files of GitHub repositories (postcss.config.*, tailwind.config.js, eslint.config.*, ...), in api.js droppers wired into package.json scripts, in fake font files run by auto-run VS Code tasks, and that rewrites commits to hide itself. The payload installs a runner that steals credentials.

  • GhostAction: malicious GitHub Actions workflows, planted with stolen tokens, that read your repository secrets and send them to an attacker.

It works on macOS, Linux and Windows, scans without executing anything, and lets an AI assistant (Claude Code, Claude Desktop, Cursor, VS Code, ...) answer "is this repo safe to run?" before you type npm run dev.

Read this first. A scan with no findings means none of the known patterns matched. It is not proof that a project or machine is safe. Indicators change quickly, the second-stage payload is downloaded at runtime, and this tool cannot see what it does. See Limitations.

Think you are already infected? Go straight to the recovery guide: how to decide between a reset and a cleanup, how to reset macOS, Windows or Linux, which credentials to rotate and in what order, and what to do if you cannot reset yet. Do the account steps from a different device.

Contents

Related MCP server: gitmoru

What it checks

Area

Examples

Rules

Source files

known loader markers; code hidden after a long whitespace run on a very long line; heavily obfuscated JS

DG-PR-001..003, 009

package.json

"dev": "node api.js && next dev" where api.js is large or obfuscated

DG-PR-004

VS Code

.vscode/tasks.json tasks with runOn: folderOpen that download code or run node on a font

DG-PR-005, 006

Fake fonts

.ttf/.otf/.woff/.woff2/.eot files that are really JavaScript

DG-PR-007

Helper files

temp_auto_push.bat

DG-PR-008

Git history

rewritten "twin" commits; committer time re-stamped into another timezone; first commit containing a marker

DG-HS-001..003

GitHub Actions

send-secrets jobs, curl posting ${{ secrets.* }}, known exfiltration hosts, toJSON(secrets), history sweeps

DG-GA-001..008

This machine

~/.node_modules runner, node -e processes, connections to known attacker IPs, startup items, shell rc files, tokens in shell history

DG-MC-001..005, DG-CR-001

GitHub account (via gh)

every branch of your repos; token scopes like workflow; which Actions secrets a malicious workflow could read

DG-CR-003, DG-GH-001

The full list, with severities and remediation, is in docs/RULES.md.

Install

devguard-mcp is not on PyPI yet. Install from source (Python 3.10 or newer):

git clone https://github.com/OWNER/devguard-mcp.git
cd devguard-mcp

# Option A: isolated command with pipx
pipx install .

# Option B: a virtual environment
python3 -m venv .venv
source .venv/bin/activate          # Windows: .venv\Scripts\activate
pip install .

devguard-mcp --version

The scanning code uses only the Python standard library. The mcp package is needed only for devguard-mcp serve.

Optional: the GitHub CLI (gh), logged in with gh auth login, for the github, token and actions commands. devguard-mcp never reads or stores your token; it only runs read-only gh commands.

Quick start (CLI)

# Scan a project before running it (safe on untrusted code: files are only read)
devguard-mcp scan path/to/project

# Look for rewritten commits and the first commit that contained a marker
devguard-mcp history path/to/project

# Only the GitHub Actions workflows
devguard-mcp workflows path/to/project

# Check this computer for the runner, suspicious processes, startup items, tokens in history
devguard-mcp machine

# Scan every branch of your GitHub repos (needs `gh`)
devguard-mcp github YOUR_USERNAME
devguard-mcp github owner/repo

# Which scopes does my gh token have? Which secrets could a malicious workflow read?
devguard-mcp token
devguard-mcp actions owner/repo

# Explain a rule, list indicators, print recovery steps
devguard-mcp rules DG-PR-003
devguard-mcp iocs
devguard-mcp checklist --machine --repos

Add --json for machine-readable output and --fail-on high to control the exit status.

Exit code

Meaning

0

nothing at or above --fail-on (default medium)

1

at least one finding at or above --fail-on

2

usage or runtime error (bad path, git/gh missing, ...)

Example output:

devguard directory scan: /work/shop
verdict: likely-malicious-indicators-found  (rules 2026-10-26)
stats: files_scanned=129, files_skipped=0

[HIGH] DG-PR-003 postcss.config.mjs:11
    Code hidden after a long whitespace run on a very long line.
    evidence: whitespace run of 336 chars, then 12319 more chars of code
[HIGH] DG-PR-004 package.json
    Script 'dev' runs 'api.js' (12564 bytes) before the real command.
    evidence: node api.js && next dev

More recipes (CI, pre-commit, scanning a fresh clone) are in docs/USAGE.md.

Use with an AI assistant (MCP)

devguard-mcp serve starts an MCP server on stdio. Find the absolute path of the command first:

which devguard-mcp        # Windows: where devguard-mcp

Claude Code

claude mcp add devguard -- /absolute/path/to/devguard-mcp serve

Claude Desktop (claude_desktop_config.json), Cursor (~/.cursor/mcp.json)

{
  "mcpServers": {
    "devguard": {
      "command": "/absolute/path/to/devguard-mcp",
      "args": ["serve"]
    }
  }
}

VS Code (.vscode/mcp.json)

{
  "servers": {
    "devguard": {
      "type": "stdio",
      "command": "/absolute/path/to/devguard-mcp",
      "args": ["serve"]
    }
  }
}

Config locations and formats change between client versions; check your client's MCP documentation if the above does not match.

Then ask, for example:

  • "Scan ~/Downloads/interview-task with devguard before I run it."

  • "Check this machine for PolinRider."

  • "Scan my GitHub account octocat for GhostAction workflows and tell me which secrets are exposed."

  • "Plan a cleanup for this repo and explain every change."

To confine the server to certain folders, set DEVGUARD_ALLOWED_ROOTS in the client's env (see Configuration).

MCP tools

Every tool is read-only and annotated readOnlyHint: true, destructiveHint: false. There is deliberately no tool that writes, deletes or executes anything.

Tool

Purpose

Network

scan_directory

PolinRider and GhostAction indicators in a folder

no

scan_git_history

twin commits, re-stamped timezones, first appearance of a marker

no

scan_workflows

GitHub Actions workflows only

no

check_machine

runner, processes, connections, startup items, shell files, tokens in history

no

scan_github

the same checks on GitHub repos and branches, via gh

yes (GitHub)

audit_github_token

scopes of the current gh login (never the token)

yes (GitHub)

audit_actions_exposure

Actions secret names and unusual run actors for a repo

yes (GitHub)

plan_cleanup

dry-run diffs with payload text redacted

no

recovery_checklist

ordered recovery steps

no

explain_rule

what a rule means and how to fix it

no

list_iocs

bundled indicators and their version date

no

Cleaning up an infected project

Removing the files is the easy part; the hard part is assuming the machine that ran the project may be compromised. Use plan_cleanup (or fix without --apply) to see what would change:

devguard-mcp fix path/to/project            # dry run: diffs only, payload text redacted
devguard-mcp fix path/to/project --apply    # asks for confirmation

--apply is CLI-only (the MCP server cannot do it). Before changing anything it copies each original into ~/.devguard-mcp/quarantine/<timestamp>/ with mode 0600 (override with --quarantine DIR). It deletes dropper files and fake fonts, strips padded payloads from config files (and re-checks that the result is clean and, when node is available, syntactically valid), and removes the node file.js && prefix from package.json scripts. Workflows and anything ambiguous are reported as manual and never edited.

After cleaning, run devguard-mcp checklist and rotate credentials from a clean device: cleanup does not undo theft. If the machine itself ran the code, cleaning files is not enough: follow the recovery guide (a reset is the reliable fix).

Safety model

This tool reads untrusted code and its output may be shown to an LLM, so it is built defensively:

  • Never executes scanned files; no eval, no import, no shell. Subprocesses (git, gh, ps, lsof, ...) use fixed argument lists.

  • Read-only by default. The only command that writes is fix --apply, and it is not exposed over MCP.

  • Output is data, not payload. Evidence is printable ASCII, whitespace-collapsed and truncated to 96 characters; tokens are shown as a 4-character prefix plus length; diffs redact long lines. This limits what a malicious repository can smuggle into an assistant's context.

  • No credentials handled. GitHub access goes through your own gh login.

  • No telemetry and no network access except the gh calls you ask for.

  • Symlinks are not followed; large files are skipped; scans have file/commit/repo caps.

More in docs/ARCHITECTURE.md and SECURITY.md.

Configuration

Variable

Effect

DEVGUARD_ALLOWED_ROOTS

os.pathsep-separated folders (: on macOS/Linux, ; on Windows). When set, MCP tools refuse paths outside them.

NO_COLOR

Disable colored CLI output.

Indicator data lives in src/devguard_mcp/data/iocs.json with a version date; devguard-mcp iocs and the list_iocs tool print it.

Limitations

  • Known patterns only. New variants will evade string markers. Structural checks (padding, auto-run scripts, fake fonts, rewritten commits) generalise better but are not complete.

  • Cannot see the second stage. It is downloaded at runtime; a machine finding means "rebuild", and a clean machine check does not prove the machine is clean.

  • History dates can be forged. DG-HS-003 shows where a marker is in history, not when an attack happened.

  • False positives exist. Rare legitimate files can match (for example generated code with a long whitespace-padded line). Low and medium findings need a human look.

  • GitHub scans are capped (25 repos, 15 branches, 400 KB per file by default) and need a working gh login. Org-level secrets need admin access to list.

  • Not a replacement for EDR, dependency scanners (npm audit, OSV) or secret scanners.

Documentation

Development

python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"

ruff check src tests && ruff format --check src tests
mypy
pytest                      # includes an end-to-end stdio MCP test; coverage must stay >= 85%

Tests use only harmless synthetic samples (filler text plus public indicator strings). Real malware must never be added to this repository.

Contributing

New indicators and false-positive reports are the most valuable contributions. See CONTRIBUTING.md and the code of conduct.

License

MIT. Provided as-is, without warranty; see the disclaimer above.

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    D
    maintenance
    Enables AI assistants to inspect local Git repositories and interact with the GitHub API for reading commits, diffs, files, issues, comments, pull requests, and project boards.
    10
    115 npm
    ISC
  • A
    license
    A
    quality
    A
    maintenance
    Enables AI to read and analyze repository incident scan results, including file diffs and summaries, to help investigate suspicious changes. It is read-only and does not execute code or send data externally.
    11
    6 npm
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Connects AI assistants to GitHub repositories, pull requests, issues, commits, and code search while enabling repository visibility controls, CI/CD monitoring, sandboxed local filesystem access, and code quality/security analysis.
    13
    1
    MIT