Malicious-package detection¶
Trustcheck produces a bounded, explainable heuristic assessment for each package. It does not declare that a package is malware.
Metadata and index signals¶
These checks run during normal inspection:
- Damerau-Levenshtein name similarity against a built-in reference set
- repeatable
--trusted-project NAMEorganization-specific references - public/private and multi-private-index dependency-confusion collisions
- maintainer and author identity changes
- ownership metadata changes
- non-overlapping declared repository changes
- release bursts, cadence acceleration, and releases after long dormancy
Dependency-confusion evidence comes from resolver and Simple Repository checks; Trustcheck does not guess index collisions from package names alone.
Resource isolation¶
Artifact responses are streamed with a 128 MiB per-response cap and a 512 MiB aggregate retained-download cap per scan. Before any archive member is read, inspection rejects archives with more than 10,000 members, more than 256 MiB of declared expansion, or a compression ratio above 200 for expansions of at least 10 MiB.
Built-in deep inspection runs in a spawned process with a 20-second wall-clock
deadline. On POSIX, that worker also receives a 15-second CPU limit, a 512 MiB
address-space limit, and drops root privileges to nobody. Other platforms
still receive process and wall-clock isolation plus the format and byte caps.
Python AST analysis¶
--inspect-artifacts parses bounded .py members with the standard-library
ast module. It detects capabilities including:
- custom setup command registration and install/build hook context
- sensitive environment, credential-file, and keyring access
- HTTP, socket, subprocess, and shell execution calls
- Python and operating-system startup persistence locations
- dynamic execution, decoding, decompression, and deserialization
Individual capabilities are usually low or medium weight. Combinations such as credential access plus networking, or install-time downloading plus process execution, receive higher scores. Source is parsed only; it is never imported or executed.
Native binary analysis¶
PE, ELF, and Mach-O members are structurally parsed for:
- target architecture
- imported DLLs, shared objects, and dylibs
- PE certificate-table or Mach-O code-signature load-command presence
- Shannon byte entropy
- embedded PE, ELF, Mach-O, ZIP, and gzip signatures
Signature presence is not signature verification. Trustcheck does not disassemble, emulate, sandbox, or execute native code.
Dynamic analysis¶
--dynamic-analysis is deliberately separate from the static archive, AST, and
native-binary checks. It executes downloaded artifacts in a disposable Docker
container, so it is never enabled by default.
The container is started with:
--network none- a non-root
65534:65534user - dropped Linux capabilities and
no-new-privileges - a read-only root filesystem plus a temporary
/tmp - one CPU, a 10-second CPU ulimit, 512 MiB RAM, a 128-process limit, and a 30-second wall-clock timeout
The dynamic runner installs the artifact into an isolated temporary virtual
environment with pip --no-deps --no-index --no-build-isolation. This may
execute untrusted build hooks, especially for source distributions. Treat the
result as behavior evidence from a constrained sandbox, not as proof of safety.
Its default Docker image is digest-pinned, and mutable image tags are rejected.
Scoring¶
Findings have their own severity, estimated confidence, rule version, estimated
false-positive prior, and score. These values are rule metadata and estimates,
not statistically validated measurements. The native JSON field is still named
false_positive_rate for schema compatibility, but it is presented as an
estimate until a measured calibration benchmark is published.
Confidence-weighted scores are combined with diminishing weight and capped at 100:
| Score | Level |
|---|---|
| 0 | none |
| 1-24 | low |
| 25-49 | elevated |
| 50-74 | high |
| 75-100 | critical |
Scores of 25 or higher create the normal
malicious_package_heuristics risk flag. Existing policy severity controls can
therefore require review or block high-scoring results.
Custom policy files can tune malicious_package_thresholds for aggregate
low, elevated, high, and critical bands, and
malicious_rule_thresholds for per-rule score contribution thresholds. For
example, setting "malicious_rule_thresholds": {"native_signature_absent": 100}
keeps unsigned native binaries in the report while preventing that weak signal
from contributing to the aggregate score.
Evaluation status¶
Trustcheck does not yet publish the reviewed artifacts or benchmark results
needed to call these rates measured. The seed manifest at
benchmarks/corpus/malicious-calibration.json defines the required versioned
corpus strata and metric contract, and is deliberately marked
seed-unmeasured.
The next security milestone is to populate that corpus with:
- known malicious PyPI releases
- typo-squats and dependency-confusion cases
- benign packages with native extensions
- legitimate packages that use subprocesses, networking, obfuscation or generated code, plugins, keyrings, custom installers, or build hooks
- deliberately weird but harmless academic and development packages
Once populated, the published benchmark must report per-rule precision, recall, false-positive rate, 95% confidence intervals, and score-band performance. Until then, Trustcheck treats every confidence value and false-positive number as an estimated rule prior, not an empirical measurement.
Interpretation¶
These signals deliberately favor explainability over a binary verdict. Legitimate packages may use networking, subprocesses, encoded resources, unsigned extension modules, or rapid release automation. Treat findings as prioritized review evidence and confirm publisher identity, source history, provenance, and behavior independently.
Every native JSON and industry export preserves the statement:
These findings are heuristic indicators for review, not proof that the package is malicious.