taintrace API Reference¶
This reference covers the public Python API and command-line interface. The
same classes are re-exported from taintrace, so either the package-level or
module-level imports shown below can be used.
Table of Contents¶
- Quick start
taintracetaintrace.lockfiletaintrace.detectortaintrace.scorertaintrace.similaritytaintrace.dbtaintrace.configtaintrace.cli- Supported lockfiles
Quick start¶
Install the package, then scan a lockfile with the high-level detector:
from pathlib import Path
from taintrace import TyposquatDetector
detector = TyposquatDetector(ecosystem="rust", similarity_threshold=0.7)
results = detector.scan(Path("Cargo.lock"))
for result in results:
if result.is_suspect:
print(result.dependency.name, result.risk_level, result.reason)
scan() returns one DetectionResult for every parsed, non-ignored
dependency. It does not raise an exception for an empty lockfile, but the
lockfile path must exist and be readable.
taintrace¶
The package exports the following public objects from taintrace.__init__:
| Name | Purpose |
|---|---|
Dependency |
A dependency extracted from a lockfile. |
LockfileParser |
Parses supported lockfile formats. |
SimilarityEngine |
Compares package names. |
KnownPackagesDB |
Provides the built-in package database. |
UnknownEcosystemError |
Raised when an ecosystem has no known-package data. |
RiskScorer |
Converts package similarity into a risk result. |
RiskResult |
Dataclass returned by RiskScorer.score. |
RiskLevel |
LOW, MEDIUM, HIGH, or CRITICAL. |
TyposquatDetector |
Scans lockfiles and individual dependencies. |
DetectionResult |
Dataclass returned by detector scans. |
The package also defines taintrace.__version__, currently a string such as
"0.2.1".
taintrace.lockfile¶
Dependency¶
A parsed dependency. ecosystem identifies the registry used for comparison,
for example "rust", "node", or "python".
from taintrace import Dependency
dependency = Dependency("serde", "1.0.210", "rust")
print(dependency.name, dependency.version)
LockfileParser¶
LockfileParser() -> LockfileParser¶
Creates a stateless parser. No network access is performed.
parse(path: Path) -> list[Dependency]¶
Detects the format from the file name and returns parsed dependencies. Unknown
file names are treated as Cargo lockfiles. Invalid JSON-based formats are
generally treated as empty input and return []; text formats can raise the
underlying OSError when the file cannot be read.
from pathlib import Path
from taintrace import LockfileParser
dependencies = LockfileParser().parse(Path("requirements.txt"))
for dependency in dependencies:
print(f"{dependency.name}=={dependency.version}")
LockfileParser.parse_go_sum(content: str) -> list[Dependency]¶
Parses Go go.sum content directly, removes /go.mod suffixes, and
deduplicates (module, version) pairs. Blank lines and comments are ignored.
from taintrace import LockfileParser
content = "example.com/tool v1.2.3 h1:checksum\n"
dependencies = LockfileParser.parse_go_sum(content)
assert dependencies[0].name == "example.com/tool"
The same function is available as the module-level alias
taintrace.lockfile.parse_go_sum(content: str).
taintrace.detector¶
DetectionResult¶
DetectionResult(
dependency: Dependency,
is_suspect: bool,
risk_level: str,
risk_score: float,
similar_packages: list[str],
similarity_scores: list[tuple[str, float]],
reason: str,
)
The result of scoring one dependency. is_suspect is true for HIGH and
CRITICAL results. risk_score is between 0.0 and 1.0.
TyposquatDetector¶
TyposquatDetector(ecosystem: str = "rust", similarity_threshold: float = 0.7) -> TyposquatDetector¶
Creates a detector backed by the embedded, offline package database.
similarity_threshold controls which similar names are considered. It is
normally between 0.0 and 1.0; values outside that range are passed to the
scoring layer without additional validation.
scan(lockfile_path: Path) -> list[DetectionResult]¶
Parses and scores a lockfile. Packages listed in discovered configuration as ignored are omitted. Raises the file-reading errors produced by the parser when the path cannot be read.
scan_dependency(name: str, version: str = "0.0.0", ecosystem: str = "rust") -> DetectionResult¶
Scores one dependency without creating a lockfile. The version is retained
in the returned Dependency, but does not affect the package-name score.
from taintrace import TyposquatDetector
result = TyposquatDetector().scan_dependency("proc-macro1", "1.0.0")
print(result.risk_level, result.risk_score, result.similar_packages)
taintrace.scorer¶
RiskLevel¶
An enum with four ordered values: LOW (0), MEDIUM (1), HIGH (2),
and CRITICAL (3). str(level) returns the uppercase name.
RiskResult¶
RiskResult(
package_name: str,
level: RiskLevel,
score: float,
similar_packages: list[tuple[str, float]],
reason: str,
)
The value returned by RiskScorer.score. similar_packages contains at most
the ten highest-scoring known packages.
RiskScorer¶
RiskScorer(db: KnownPackagesDB | None = None) -> RiskScorer¶
Creates a scorer. If db is omitted, a new built-in KnownPackagesDB is
created.
score(package_name: str, ecosystem: str = "rust", similarity_threshold: float = 0.7) -> RiskResult¶
Scores a package name. Known packages receive LOW and score 0.0; unknown
packages with no match receive MEDIUM and score 0.3. Similarity thresholds
of at least 0.95, 0.85, or 0.75 produce CRITICAL, HIGH, or MEDIUM
respectively; lower matches are LOW.
from taintrace import RiskScorer
result = RiskScorer().score("reqeusts", ecosystem="python")
assert 0.0 <= result.score <= 1.0
print(result.level, result.reason)
taintrace.similarity¶
SimilarityEngine¶
similarity(name1: str, name2: str) -> float¶
Returns a combined similarity score between 0.0 and 1.0, using edit
distance, Soundex, substring matching, and a small set of Unicode confusables.
Identical strings return 1.0.
The Soundex component is damped when the two names are long enough for the
four-character code to saturate: Soundex emits a fixed four-character code, so
long names sharing a prefix collide (org.* coordinates all hash to o621).
Without that guard a collision scored 1.0 and unrelated packages were
reported as perfect typosquats.
could_match(name1: str, name2: str, threshold: float) -> bool¶
A cheap necessary condition for similarity(name1, name2) >= threshold, used to
skip candidates that cannot possibly reach the threshold. It returns True for
a superset of the real matches: name-length compatibility covers the edit
distance, substring and homoglyph components, and a close Soundex code covers
the phonetic one.
levenshtein(s1: str, s2: str) -> int¶
Returns the minimum number of insertions, deletions, and substitutions needed to change one string into the other.
normalize_homoglyphs(s: str) -> str¶
Lowercases a string and replaces the engine's supported high-confidence confusable characters with ASCII equivalents.
from taintrace import SimilarityEngine
engine = SimilarityEngine()
assert engine.levenshtein("kitten", "sitting") == 3
assert engine.similarity("proc-macro1", "proc-macro2") > 0.9
print(engine.normalize_homoglyphs("pаypal"))
taintrace.db¶
KnownPackagesDB¶
KnownPackagesDB() -> KnownPackagesDB¶
Loads the built-in package names into memory. The database is offline and
cannot be populated through the public API. The java ecosystem is loaded
from packaged data (taintrace/data/java_packages.txt), generated from the
Maven Central repository index by scripts/generate_java_packages.py.
is_known(name: str, ecosystem: str = "rust") -> bool¶
Returns whether name is present in the selected ecosystem's known-package
list, case-insensitively. Unknown ecosystems return False. For java, a
bare artifactId also resolves against every curated groupId.
has_ecosystem_data(ecosystem: str) -> bool¶
Returns whether the ecosystem has any known-package data at all. An ecosystem registered but empty silently disables similarity matching (#77); this makes that condition detectable.
ecosystems() -> list[str]¶
Returns the ecosystems that have known-package data, sorted.
get_similar(name: str, threshold: float = 0.8, ecosystem: str = "rust") -> list[tuple[str, float]]¶
Returns up to ten known package names whose similarity is at least
threshold, sorted from highest to lowest score.
Raises taintrace.UnknownEcosystemError when ecosystem has no data, so a
coverage gap is never reported as an empty match list.
from taintrace import KnownPackagesDB
db = KnownPackagesDB()
assert db.is_known("Serde", "rust")
matches = db.get_similar("proc-macro1", ecosystem="rust")
print(matches[:1])
taintrace.config¶
Configuration values use the keys threshold, format or output_format,
ecosystem, no_informational, and ignore. Invalid keys and invalid values
are discarded by validate_config.
interpolate_env_vars(data: Any) -> Any¶
Recursively replaces $VAR and ${VAR} in strings nested in dictionaries and
lists. Missing variables are left unchanged; non-container values are returned
as-is.
parse_config_file(path: Path) -> dict¶
Parses TOML, YAML, or JSON based on the file name and returns an interpolated
dictionary. A missing or unsupported file returns {}. Invalid JSON and YAML
are treated as empty configuration; TOML parser errors may propagate.
validate_config(config: dict) -> dict¶
Filters and normalizes supported configuration keys. Thresholds outside
0.0..1.0, unsupported formats/ecosystems, and invalid threshold values are
ignored.
find_default_config(cwd: Path | None = None) -> Path | None¶
Searches the current directory, its parents, and user-level configuration
paths. Returns the first matching file or None.
load_config(config_path: Path | str | None = None, cwd: Path | None = None) -> dict¶
Loads and validates an explicitly supplied file, or discovers a default file
when no path is supplied. A directory passed as config_path is treated as
cwd; no configuration found returns {}.
get_ignored_packages(cwd: Path | None = None, config_path: Path | None = None) -> list[str]¶
Returns the configured ignore list, or [] when no configuration is found.
from pathlib import Path
from taintrace.config import load_config
config = load_config(Path(".taintrace.toml"))
print(config.get("threshold", 0.7))
taintrace.cli¶
The CLI is implemented with Click and is installed as the taintrace
executable. These commands are the stable user-facing interface:
taintrace check Cargo.lock --format json
taintrace score proc-macro1 --ecosystem rust
taintrace scan-directory . --format sarif
cli() -> None¶
Click group entry point. It provides --version and dispatches the commands
below. It is normally invoked through the installed taintrace executable.
check(lockfiles: tuple[Path, ...], config_file: Path | None, output_format: str, threshold: float, ecosystem: str, no_informational: bool, ignore: tuple[str, ...]) -> None¶
Click command that checks one or more existing lockfiles. output_format is
cli, json, or sarif; threshold is between 0.0 and 1.0; ecosystem
can be auto, rust, node, python, go, ruby, php, swift,
elixir, or java. config_file is optional and ignore can contain
multiple package names. It exits 2 when no lockfile is supplied and 1
when suspects are found.
score(name: str, ecosystem: str = "rust") -> None¶
Click command that prints the risk assessment for one package name. The
ecosystem must be one of rust, node, python, go, ruby, php,
swift, or elixir.
scan_directory(path: Path = Path("."), config_file: Path | None = None, output_format: str = "cli", threshold: float = 0.7, no_informational: bool = False, ignore: tuple[str, ...] = ()) -> None¶
Click command that recursively scans supported lockfiles below path,
skipping .git, node_modules, vendor, dist, build, and cache
directories. It prints a no-lockfiles message and returns when none are
found. Its output and exit behavior otherwise match check.
check LOCKFILE...parses one or more lockfiles and reports suspects.score NAMEscores one package name.scan-directory PATHrecursively discovers and scans supported lockfiles.
Common options include --config, --threshold, --format (cli, json,
or sarif), and repeated --ignore. The process exits with 0 when no
suspects are found and 1 when at least one suspect is found. Invalid paths
or command-line arguments are reported by Click.
Supported lockfiles¶
LockfileParser.parse recognizes these file names:
| Ecosystem | File names |
|---|---|
| Rust | Cargo.lock, Cargo.toml |
| Node.js | package-lock.json, pnpm-lock.yaml, yarn.lock, bun.lock, bun.lockb |
| Python | requirements.txt, poetry.lock, uv.lock, pyproject.toml, Pipfile.lock |
| Go | go.sum |
| Ruby | Gemfile.lock |
| PHP | composer.json, composer.lock |
| Swift | Package.resolved, Package.swift |
| Elixir | mix.lock |
| Java/Gradle | build.gradle, build.gradle.kts, libs.versions.toml |
bun.lockb is binary and returns no dependencies with a UserWarning; convert
it to bun.lock before scanning for useful results.