Metadata-Version: 2.4
Name: skilltotal
Version: 0.57.0
Summary: AI Component Security Platform — static security analysis for AI components (CLI engine)
Author: SkillTotal
License: Apache-2.0
Project-URL: Homepage, https://www.skilltotal.ai
Project-URL: Repository, https://github.com/pezhik/skilltotal
Project-URL: Documentation, https://github.com/pezhik/skilltotal/tree/main/docs
Project-URL: Issues, https://github.com/pezhik/skilltotal/issues
Project-URL: Changelog, https://github.com/pezhik/skilltotal/blob/main/CHANGELOG.md
Keywords: security,ai,mcp,supply-chain,static-analysis,prompt-injection,agentic-ai,agent-security,ai-security,cloud-security-alliance,csa,maestro,mitre-atlas,llm-security
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: NOTICE
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: ruff>=0.4; extra == "dev"
Requires-Dist: jsonschema>=4.0; extra == "dev"
Requires-Dist: bandit>=1.7; extra == "dev"
Requires-Dist: build>=1.0; extra == "dev"
Requires-Dist: twine>=5.0; extra == "dev"
Requires-Dist: pre-commit>=3.5; extra == "dev"
Requires-Dist: detect-secrets>=1.5; extra == "dev"
Requires-Dist: urllib3>=2.8.0; extra == "dev"
Requires-Dist: virtualenv>=21.7.13; extra == "dev"
Dynamic: license-file

<!-- mcp-name: ai.skilltotal/skilltotal -->

# SkillTotal

[![PyPI](https://img.shields.io/pypi/v/skilltotal)](https://pypi.org/project/skilltotal/)
[![Python](https://img.shields.io/pypi/pyversions/skilltotal)](https://pypi.org/project/skilltotal/)
[![License](https://img.shields.io/pypi/l/skilltotal)](LICENSE)
[![CI](https://github.com/pezhik/skilltotal/actions/workflows/ci.yml/badge.svg)](https://github.com/pezhik/skilltotal/actions/workflows/ci.yml)
[![GitHub Marketplace](https://img.shields.io/badge/Marketplace-SkillTotal-2ea44f?logo=githubactions&logoColor=white)](https://github.com/marketplace/actions/skilltotal-ai-component-security-scan)
[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/pezhik/skilltotal/badge)](https://scorecard.dev/viewer/?uri=github.com/pezhik/skilltotal)

**AI Component Security Platform — open-source CLI engine.**

SkillTotal statically analyzes AI-related components — agent skills/plugins, MCP servers, npm /
Python packages, repositories, and **AI-generated projects you upload as an archive or file** — to
surface supply-chain risks, dangerous capabilities, prompt-injection surfaces, and data-exfiltration
paths **before** the component is installed or trusted. Point it at a path, a git URL, an
`npm:` / `pypi:` package, or a project archive (`.zip` / `.tar.gz`) / single file.

**Try it online (no install, no account):** [www.skilltotal.ai](https://www.skilltotal.ai) —
the website runs this same engine. Prefer the CLI? `pipx install skilltotal` (below).

It analyzes **only the component itself** — never your user, company, environment,
deployment, or runtime context. Every score and finding is derived exclusively from the
files inside the component.

> Core principle: **every confirmed finding carries evidence** (file, line range, code
> snippet). Anything that cannot be evidenced is placed in `needs_review`, never in
> `findings`, and never affects the score.

## Why SkillTotal

- **Checks packages before your agent installs them.** The Claude Code plugin reads each `npx`,
  `npm install`, `pip install` or `claude mcp add` command the agent is about to run. It blocks
  packages with malicious indicators and asks you before installing high- or critical-risk ones.
  [Set it up](#claude-code-plugin).
- **100% local & offline** — the component's code **never leaves your machine**. No account,
  no API token, no cloud upload (unlike cloud scanners that send your components to a backend).
- **Safe to point at untrusted components** — the engine analyzes without ever running them on
  your machine. (Optional dynamic analysis is a separate paid service that runs only in our
  isolated sandbox, with your consent.)
- **Zero runtime dependencies**, pure Python stdlib — auditable and easy to vendor/air-gap.
- **Deterministic** — regex + AST, no LLM in the static engine; the same input always yields
  the same report.
- **Evidence-anchored & low false-positive** — every finding points at an exact file:line.
- **Standards-aligned** — every component gets a behavioral **trait fingerprint** mapped to the
  **Cloud Security Alliance (CSA)** agentic threat model, **MAESTRO** threat-model layers, and
  **MITRE ATLAS** tactics — including a three-way *execution-context* read (embedded static
  credential → delegated OAuth/OIDC → least-privilege scoped identity) that shows the blast radius
  of a compromise, not just that a secret exists.
- **Free and open source** (Apache-2.0) — the full static report is free, forever.

## Measured, not asserted

Detection claims are cheap, so the numbers behind them are published with the data and the code
that produced them.

- **[The whole MCP registry, scanned](docs/mcp-registry-survey.md)** — every distinct component in
  the official registry, 17,535 of them, in one deterministic run.
  88.2% expose tools to an agent, 64.8% can reach the network, 21.1% can execute shell commands —
  and the risk distribution underneath is far flatter, because a capability scores zero here.
  [Raw JSON](docs/mcp-registry-survey.json) · [the harness](tests/manual_eval/survey_registry.py).
- **[Detection efficacy](docs/efficacy-report.md)** — recall and precision on a labelled corpus,
  regenerated every release and enforced by CI as a floor.

Every one of these reproduces: same input, same engine, same output. Nothing is executed and no
LLM is involved.

## Install

Requires **Python 3.10+**. Zero runtime dependencies. `git` is required only for scanning
remote URLs.

Recommended for the CLI — [pipx](https://pipx.pypa.io) (isolated install; also works on
Debian/Ubuntu where bare `pip install` is blocked by PEP 668):

```bash
pipx install skilltotal
```

Or into a virtual environment / as a library:

```bash
pip install skilltotal
```

From source (development):

```bash
pip install -e ".[dev]"
```

## Claude Code plugin

Coding agents install packages without asking you. The plugin checks each package before the
install command runs, using the same engine on your machine. If a package has malicious
indicators, the command is denied and the agent is told why. A high- or critical-risk package
needs your approval, and so does a package from a custom registry or a direct archive URL,
because SkillTotal can only scan the copy on the public registry. Clean packages install as
usual.

The repository doubles as a plugin marketplace, so inside Claude Code run:

```
/plugin marketplace add pezhik/skilltotal
/plugin install skilltotal@skilltotal
```

Then run `/reload-plugins` or restart Claude Code. The hook loads along with the plugins, so
until you do, it does not check install commands in the session where you ran `/plugin install`.

The plugin calls the CLI, so you also need `pip install skilltotal` (0.56.3 or later). If the CLI
is missing or fails to start, install commands run unchecked and nothing is blocked. Claude Code
shows a warning on each one, so you can tell a broken setup from a clean check.

Install the CLI where Claude Code can find it: `skilltotal --version` should work in the terminal
you start Claude Code from. To see the hook work without touching a real package, ask the agent to
run `npm install --registry https://registry.example.invalid left-pad`. Claude Code should stop and
ask you, with SkillTotal's reason. Nothing is installed unless you approve.

On Windows, Claude Code runs most commands through its PowerShell tool. Before each command the
agent runs through the Bash or PowerShell tool, a hook looks for packages the command would install:
`npx`, `bunx`, `pnpm dlx`, `npm`/`pnpm`/`yarn`/`bun` add or install, `pip`, `uv`, `uvx`, `pipx`,
and `claude mcp add … -- <command>`. Other commands pass straight through. If a package has
malicious indicators, the command is denied and the agent sees why. A high- or critical-risk
package needs your approval. A clean one installs as usual, and the agent gets a one-line note
with its score.

A package from a custom registry or index (`--registry`, `--index-url`, `--extra-index-url`) or a
direct archive URL always needs your approval, because SkillTotal can only scan the copy on the
public registry, and that may not be the one that gets installed. Packages from GitHub
(`github:owner/repo`, `git+https://github.com/...`) are scanned from the repository.

All checks for one command share a 20-second budget (set `SKILLTOTAL_HOOK_BUDGET` to change
it). A package that isn't checked in time, or whose check fails, never blocks the install, and
the agent is told it wasn't checked. Verdicts are reused for 24 hours and redone when the engine
version changes, so repeated `npx tsc` or `npx prettier` calls don't trigger a rescan. The
plugin also adds a `/skilltotal:scan <target>` command and registers the MCP server described
below.

The hook reads a command the way the shell would, through `sudo`, `env`, `bash -c '...'`,
`cmd /c`, `$(...)`, groups like `(npm i y)` and chains like `cd x && npm i y`. For PowerShell it
also follows `& { ... }`, `iex '...'`, `Start-Process npm -ArgumentList ...` and
`powershell -EncodedCommand`. It only sees what the command spells out,
so a command that builds the package name at run time, or a script the agent downloads and runs,
gets past it. For code you don't trust, run the agent in a container.

## Usage

```bash
# Human-readable report
skilltotal scan ./path/to/component

# Scan a remote repository (shallow git clone)
skilltotal scan https://github.com/owner/repo

# Scan a project archive or a single file (e.g. an AI-generated project downloaded as a ZIP)
skilltotal scan ./my-project.zip
skilltotal scan ./app.tar.gz
skilltotal scan ./suspicious.py

# Scan a package from a registry (latest, or a pinned version)
skilltotal scan npm:left-pad
skilltotal scan npm:left-pad@1.3.0
skilltotal scan pypi:requests
skilltotal scan pypi:requests==2.31.0

# JSON to stdout
skilltotal scan ./component --json

# SARIF 2.1.0 (GitHub Code Scanning / IDE)
skilltotal scan ./component --sarif --output report.sarif

# Write the report to a file (SARIF if --sarif, else JSON)
skilltotal scan ./component --output report.json

# CI gate: exit code 2 by severity level or by risk score
skilltotal scan ./component --fail-on-high             # alias for --fail-on high
skilltotal scan ./component --fail-on medium
skilltotal scan ./component --fail-on-score 50

# Skip paths (repeatable; combined with the config file's `exclude`)
skilltotal scan ./component --exclude "vendor/*" --exclude "*.min.js"

# Opt-in provenance for npm:/pypi: sources (registry metadata -> needs_review, never scored)
skilltotal scan npm:some-lib --provenance

# Baseline: snapshot current findings, then suppress them on later scans
skilltotal scan ./component --write-baseline .skilltotal-baseline.json
skilltotal scan ./component --baseline .skilltotal-baseline.json --fail-on-high

# Diff two versions of a component: what changed between them?
# Each side is any scannable source (path/archive/git/npm:/pypi:) or a saved --json report.
skilltotal diff npm:some-lib@1.2.3 npm:some-lib@1.2.4
skilltotal diff ./old-checkout ./new-checkout --json
skilltotal diff old-report.json new-report.json
# CI gate: fail (exit 2) if the new version INTRODUCES a high/critical finding
skilltotal diff npm:some-lib@1.2.3 npm:some-lib@1.2.4 --fail-on-new high

# Pre-install guard: allow/block decision (exit 2 on block) you can chain before installing
skilltotal guard npm:some-mcp-server && claude mcp add some-mcp-server -- npx some-mcp-server
skilltotal guard --installed            # check every AI component already on this machine
skilltotal guard npm:x --block-on malicious   # block only on malicious indicators

# Inventory: discover AI components already installed on this machine and scan them
# (reads agent configs for Claude Desktop/Code, Cursor, Windsurf, VS Code, Gemini, and
#  local skills; derives an npm:/pypi:/local source per MCP server and runs the engine)
skilltotal inventory
skilltotal inventory --json
skilltotal inventory --no-scan          # list only, do not scan
skilltotal inventory --project .        # also include this project's agent configs
skilltotal inventory --sbom             # AI-BOM: CycloneDX 1.6 JSON of your agent stack,
                                        # scan verdicts attached as component properties

# List every detection rule
skilltotal rules list
skilltotal rules list --json
```

**Baseline** suppresses findings by a stable fingerprint of
`(rule id, file, code snippet)` — independent of line numbers, so it survives edits.
Suppressed findings are removed before scoring and do not affect the risk score.

**Diff** reports new / resolved / changed findings, evidence-level additions and removals
(matched by the same line-independent fingerprint as the baseline, so pure line shifts are
not noise), capability changes, and the risk-score delta. `--fail-on-new LEVEL` gates only
on risk the new version *introduces* — existing accepted findings never trip it, so it fits
upgrade reviews ("is 1.2.4 riskier than the 1.2.3 we already vetted?") without a baseline
file.

**Guard** is the install-time answer to "should I trust this component right now?".
Malicious indicators always block; scored risk at/above `--block-on` blocks;
**capabilities alone never block** — a legitimate MCP server with shell/network access
passes, so the guard stays quiet enough to leave enabled everywhere (unlike a raw
`--fail-on high` gate, which would trip on most of the ecosystem's honest capability
findings).

**Provenance** (`--provenance`, opt-in) adds registry-metadata signals for `npm:` /
`pypi:` sources: *recently published*, *deprecated / yanked*, *no recent releases*, *no
repository link*. Metadata is context about a component, not component content — so these
signals go to `needs_review` and **never** affect the score or verdict, and the default
scan stays 100% component-only and offline.

**Project config** (optional) — commit a `.skilltotal.toml` instead of repeating flags
(CLI flags override it):

```toml
fail_on = "high"           # low | medium | high | critical
fail_on_score = 50         # or gate on the 0-100 risk score
exclude = ["vendor/*", "*.min.js"]
ignore = ["ST-NET-PY"]     # rule ids to drop
baseline = ".skilltotal-baseline.json"

# Per-rule policy: reviewable gate decisions that live in the repo, not in a dashboard.
[policy]
"ST-SHELL-PIPE-EXEC" = "block"   # gate trips (exit 2) whenever this rule fires,
                                 # even with no fail_on configured
"ST-DYN-PY" = "warn"             # explicit accept-but-show: reported, still counts toward
                                 # the risk score, but exempt from the fail_on severity gate
"ST-SENS-WORD" = "ignore"        # suppressed entirely (same effect as `ignore`)
```

Suppress a single finding inline with a `# skilltotal:ignore` (or `# skilltotal:ignore[ST-ID]`)
comment on its line.

`python -m skilltotal ...` works identically to the `skilltotal` console script.

### Exit codes

| Code | Meaning |
|------|---------|
| 0 | Success |
| 1 | Usage / collection error (e.g. path missing, clone failed) |
| 2 | A configured gate tripped (`--fail-on`/`--fail-on-high` severity, `--fail-on-score`, or `diff --fail-on-new`) |

> **Gate semantics:** `--fail-on`/`--fail-on-high` trip on the **severity of any single finding**,
> not the aggregate `risk_score`. A component can report `risk_level: low` (score 0) and still fail
> the gate if it has a high-severity finding — including a powerful *capability* (e.g. shell or
> network access), which is reported but never scored as malicious. To gate on the score instead,
> use `--fail-on-score`; to accept known findings, use a baseline, an inline
> `# skilltotal:ignore[ST-ID]`, or a per-rule `[policy]` action (`block` / `warn` / `ignore`).

## CI / GitHub Action

Run SkillTotal in CI and surface findings in your repository's **Security → Code scanning** tab.

```yaml
# .github/workflows/skilltotal.yml
name: SkillTotal
on: [push, pull_request]
permissions:
  contents: read
  security-events: write   # required to upload SARIF to Code Scanning
  pull-requests: write     # required only for comment-on-pr (optional)
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pezhik/skilltotal@v0.57.0
        with:
          source: .             # a path, a git URL, or an npm:/pypi:<name> spec
          fail-on: high         # fail the build on a high/critical finding (or 'none')
          comment-on-pr: 'true' # post a sticky summary comment on pull requests (optional)
```

The action installs the CLI, scans `source`, uploads SARIF (so findings appear inline on pull
requests and in Code Scanning), and fails the job on a high/critical finding unless
`fail-on: none`. On pull requests, `comment-on-pr: 'true'` posts a single summary comment (risk
level, score, findings, capabilities) and updates it in place on later runs — it needs
`pull-requests: write` and is off by default. Pin the action to a released tag (see
[Releases](https://github.com/pezhik/skilltotal/releases)) and, optionally, pin the engine version
with the `version:` input. Prefer plain CLI? It is the same thing:
`skilltotal scan . --sarif --output skilltotal.sarif --fail-on-high`.

### Use as a pre-commit hook

Run SkillTotal on every commit via [pre-commit](https://pre-commit.com):

```yaml
# .pre-commit-config.yaml
repos:
  - repo: https://github.com/pezhik/skilltotal
    rev: v0.57.0
    hooks:
      - id: skilltotal
        args: [".", "--fail-on-high"]   # scan the repo; block the commit on a high/critical finding
```

Then `pre-commit install`. The hook installs the CLI in its own environment and scans the repo
on commit; tune the scan with the same flags as the CLI (e.g. `--exclude`, `--fail-on`).

### Use as an MCP server

Let your agent check a component *before* installing it. `skilltotal mcp` runs the engine
as a stdio MCP server (stdlib-only, still zero dependencies) — register it in Claude
Code/Desktop, Cursor, Windsurf, or any MCP client:

```json
{ "mcpServers": { "skilltotal": { "command": "skilltotal", "args": ["mcp"] } } }
```

Tools exposed: `scan_component` (full report for a path / git URL / `npm:` / `pypi:`
source), `diff_components` (upgrade review: what changed between two versions), and
`list_rules`. Scans run locally with the same never-execute static engine — the component's
code is not uploaded anywhere.

### Add a status badge

Scan a component on [skilltotal.ai](https://www.skilltotal.ai) and each report offers an
**"Add this badge"** snippet — a small SVG that always reflects the component's latest scan and
links back to the full report. Drop it in your README so visitors see the risk at a glance:

```markdown
[![SkillTotal](https://www.skilltotal.ai/…/badge?source=npm:your-package)](https://www.skilltotal.ai)
```

Copy the exact, ready-to-paste markdown from the report page — it fills in the badge URL for you.

## Methodology

SkillTotal performs **static** security analysis of AI components — MCP servers, agent
skills/plugins, npm and PyPI packages, and AI-generated projects/repositories. The engine
combines capability analysis, dangerous-pattern detection, privilege analysis, supply-chain
(install-time) analysis, prompt-surface analysis, and data-flow correlation (e.g. secret
access combined with network egress). Findings are mapped to risk categories and contribute to
a **0–100 risk score**; capabilities are reported but never inflate the score — capability ≠ risk.
Nothing is executed and no LLM is called, so results are deterministic and reproducible.

## What it detects

| Category | Examples |
|----------|----------|
| Shell execution | `subprocess.*`, `os.system`, `child_process.exec` |
| Filesystem access | `open`, `read_text`/`write_text`, `fs.readFile`/`writeFile` |
| Sensitive paths | `~/.ssh`, `~/.aws`, `.env`, `id_rsa`, `credentials`, `secrets` |
| Network egress | `requests`, `urllib`, `aiohttp`, `fetch`, `axios` |
| Install-time execution | npm `preinstall`/`postinstall`/`prepare`, `setup.py` hooks |
| Dynamic code execution | `eval`, `exec`, `compile`, `new Function`, `vm.runInNewContext` |
| Obfuscation | decode-and-execute chains, base64 blobs, hex escaping, minification |
| MCP risks | manifests, dangerous tools (shell/fs/network/credential), server commands |
| Prompt surface | "ignore previous instructions", "reveal system prompt", exfiltration phrasing |

### Coverage by component type

Legend: **✅** analyzed by default for this component type · **⚠️** the engine detects this, but
that surface is uncommon for this type — so it is flagged only when the component actually contains
it (e.g. prompt-injection text inside an npm/PyPI package) · **❌** not applicable to this type ·
**🚧** planned (SkillTotal Cloud).

Columns are the component types SkillTotal scans. **AI project** = a scanned repository or folder
— an agent skill/plugin, an AI-generated codebase, or a set of prompts/configs — that is not a
published npm/PyPI package.

| Category | MCP | npm | PyPI | AI project |
|---|---|---|---|---|
| Prompt injection / instruction override | ✅ | ⚠️ | ⚠️ | ✅ |
| Tool poisoning (MCP tool metadata) | ✅ | ❌ | ❌ | ⚠️ |
| Dangerous capabilities (shell / fs / network) | ✅ | ✅ | ✅ | ⚠️ |
| Data exfiltration (secret access + egress) | ✅ | ✅ | ✅ | ⚠️ |
| Secret theft / sensitive-path access | ✅ | ✅ | ✅ | ⚠️ |
| Dynamic code execution | ✅ | ✅ | ✅ | ⚠️ |
| Obfuscation (decode-and-execute) | ✅ | ✅ | ✅ | ✅ |
| Hidden-Unicode smuggling | ✅ | ✅ | ✅ | ✅ |
| Embedded secrets (hardcoded keys/tokens) | ✅ | ✅ | ✅ | ✅ |
| Install-time / supply-chain hooks | ⚠️ | ✅ | ✅ | ❌ |
| Overprivileged / auto-approved tools | ✅ | ❌ | ❌ | ⚠️ |
| Runtime behavior analysis | 🚧 | 🚧 | 🚧 | 🚧 |
| Sandbox analysis | 🚧 | 🚧 | 🚧 | 🚧 |

### Typical findings

- An MCP tool can execute arbitrary shell commands
- A package downloads and runs code from an external URL
- Access to credential locations (`~/.aws`, `~/.ssh`, `.env`) detected
- Dynamic code execution (`eval` / `exec`) detected
- Prompt-injection / instruction-override phrasing in a tool description or skill
- Sensitive-data access combined with outbound network egress
- Hardcoded API keys or tokens
- An MCP server with auto-approved or overprivileged tools
- Untrusted input (environment, `sys.argv`, a request/response body) flowing into `exec` or a
  shell — a proven injection path, not just a dangerous API in isolation
- An **agent skill** does more than its declared `allowed-tools` allow (undeclared capability /
  least-privilege violation)

## Out of scope

SkillTotal statically analyzes a **single component's own files**. It does not execute code,
observe runtime behavior, or assess your environment, deployment, or infrastructure. It is **not**
a substitute for:

- a penetration test
- an application-security (app-sec) review
- an architecture / design review
- a cloud-security or infrastructure assessment
- a Kubernetes / container runtime audit
- a business-logic review
- a manual code review

Runtime behavior and sandbox analysis are planned for **SkillTotal Cloud** (paid).

## Output

A normalized report containing the component identity, a **risk score (0–100)** and
**risk level** (low / medium / high / critical), detected **capabilities** (each
evidence-backed), a behavioral **trait fingerprint** (with a CSA / MAESTRO / MITRE ATLAS
crosswalk), **findings**, **needs_review**, and **metadata**. See
[docs/report-schema.md](docs/report-schema.md) and [docs/scoring.md](docs/scoring.md).

Every finding also carries its **OWASP Agentic Skills Top 10** category ids (`owasp`), emitted in
both the JSON report and SARIF (native `taxonomies`/`relationships`);
[docs/owasp-agentic-skills-mapping.md](docs/owasp-agentic-skills-mapping.md) explains the coverage
(AST01–AST05) and the honest gaps. For MCP servers,
[docs/mcp-owasp-mapping.md](docs/mcp-owasp-mapping.md) maps SkillTotal's checks to the OWASP MCP
Security Cheat Sheet (and names the runtime controls a static engine can't cover).

The report's **`traits`** array is a behavioral fingerprint — a higher-level projection over the
findings (e.g. `execution_authority`, `embedded_credential`, `untrusted_perception`, and the
*emergent* `exfil_correlation` combination) — each mapped to the Cloud Security Alliance
trait-based model, a MAESTRO threat-model layer, and a MITRE ATLAS tactic where there is an honest
fit. It is descriptive and never affects the score; see
[docs/trait-crosswalk.md](docs/trait-crosswalk.md).

## Architecture

The package under `skilltotal/` (except `cli.py`) is a pure, side-effect-free library so the
same engine can power the future web app and enterprise SaaS. See
[docs/architecture.md](docs/architecture.md).

## Development

```bash
pip install -e ".[dev]"
pytest
```

## Accuracy notes

- Python is analyzed via an **AST** (resolves import aliases, tells `open(p,'w')` from a
  read, ignores API names that only appear in strings/comments). Node.js/config use regex.
- **Test code** (`__tests__/`, `*.test.*`, `tests/`, `conftest.py`, …) is demoted to
  `needs_review` — it is not executed by consumers, so it does not affect the score.
- Ambiguous signals (bare `secrets`/`credentials` words, lone base64 blobs, "before
  answering" phrasing, minified files) go to `needs_review`, never to `findings`.
- **Hidden Unicode** (ASCII-smuggling tag characters, Trojan-Source bidi overrides,
  zero-width chars) is detected and decoded — a real evasion used to smuggle instructions
  past human review. See `tests/manual_eval/` for calibration against real-world attacks.
- Shell execution covers `subprocess`/`os.system`, `asyncio.create_subprocess_*`, Node
  `child_process`, and common process-spawning libraries (Python `sh`/`plumbum`/`pexpect`/
  `invoke`/`fabric`; Node `zx`/`execa`/`cross-spawn`/`shelljs`/`tinyexec`/`node-pty`).
- MCP dangerous tools are classified by name/description both in JSON manifests **and when
  defined in code** (`server.tool("run_command", …)`, `@mcp.tool` over `def read_file`).
- **Limitations:** detection is at the call/import level. Capability via an *unrecognized*
  higher-level library (e.g. a git library that writes files internally, a browser library)
  may not be flagged as a raw filesystem/shell call. Capabilities indicate *presence*, not
  proven misuse.

## Open source vs SkillTotal Cloud

SkillTotal is **open core**. This engine (analysis + all detection rules + CLI) is open source
and **complete on its own** — run it locally or in CI, free, offline, with zero runtime
dependencies. It tells you **what** a component does, with evidence.

Paid features are delivered only via **SkillTotal Cloud** (the website) and explain **why it
matters**: LLM interpretation and prioritization of findings, dynamic sandbox execution,
hosting, scan history, and monitoring. They are server-side services on top of this engine —
their code is not part of this repository. See [docs/open-core.md](docs/open-core.md).

## License

[Apache-2.0](LICENSE). See also [NOTICE](NOTICE).
