Performance and extensibility¶
Bounded concurrency¶
--workers N bounds target scans, artifact downloads, provenance requests,
static archive checks, advisory providers, OSV advisory fetches, and enrichment
work. The default is 8; accepted values are 1 through 64, or -1 for all
available CPU cores.
trustcheck scan -f requirements.txt --with-osv --workers 8
Results remain in resolved-input order even when workers finish out of order. Each target uses an isolated mutable PyPI client while sharing the persistent content store, digest-keyed artifact cache, advisory cache, and bounded HTTP connection pool. Concurrent requests for the same artifact digest are coalesced into one download.
Artifact and provenance work is predominantly network and archive I/O. CUDA or other GPU acceleration would add transfer and deployment overhead without speeding up these operations, so Trustcheck uses bounded CPU threads instead.
The same setting is available as TRUSTCHECK_MAX_WORKERS,
performance.max_workers in the JSON config file, and the GitHub Action
workers input.
Artifact scope¶
scan defaults to --artifact-scope target. Trustcheck ranks wheel tags for
the requested Python, ABI, implementation, and platform, inspects the best
compatible non-yanked wheel, and falls back to one sdist when no compatible
wheel exists. This avoids downloading artifacts the target cannot install.
Use --artifact-scope sdist for source-only review. Use
--full --artifact-scope all when reviewing every file published for a release.
OSV batch queries¶
Multi-package scans use OSV /v1/querybatch in chunks of at most 1,000
queries. Trustcheck follows each query's pagination token, deduplicates
advisory IDs, and fetches every full advisory record once with bounded
concurrency. OSV-compatible providers without the batch endpoint fall back to
bounded individual queries.
Content-addressed cache¶
--cache-dir stores response bodies by SHA-256 under
objects/sha256/<prefix>/<digest>. Request references live separately under
refs/. Reads verify both the object size and SHA-256 digest; corrupted or
missing objects fail closed with cache_integrity_failed.
This layout deduplicates identical responses and artifact bytes while keeping request lookup deterministic. Legacy request-addressed cache files remain readable during migration.
Advisory snapshots¶
Use a versioned snapshot to make advisory results portable and available to offline scans:
trustcheck scan -f requirements.txt \
--with-osv \
--write-advisory-snapshot .trustcheck/advisories.json \
--sign-advisory-snapshot
trustcheck scan -f requirements.txt \
--offline \
--cache-dir .trustcheck/cache \
--advisory-snapshot .trustcheck/advisories.json \
--advisory-snapshot-identity \
https://github.com/example/project/.github/workflows/snapshot.yml@refs/heads/main \
--advisory-snapshot-issuer https://token.actions.githubusercontent.com \
--max-advisory-age 24
--advisory-snapshot is repeatable. Inputs merge deterministically and
deduplicate advisory identities. --write-advisory-snapshot writes the merged
set atomically using schema urn:trustcheck:advisory-snapshot:2.0.0.
Schema 2 records a source manifest with provider URLs and a bound SHA-256
digest of canonical advisory records, plus generation and expiration
timestamps. --sign-advisory-snapshot
creates a Sigstore bundle beside the JSON as <snapshot>.sigstore.json using
ambient OIDC identity. Loading verifies that bundle against
--advisory-snapshot-identity and optional --advisory-snapshot-issuer before
parsing records. --max-advisory-age HOURS defaults to 168 and can impose a
shorter lifetime than the signed snapshot. Legacy or unsigned snapshots are
accepted only with --allow-unsigned-advisory-snapshot.
The snapshot covers vulnerability intelligence. Offline package metadata and artifacts still require a populated content cache.
Resumable scans¶
scan and environment accept --resume-state PATH:
trustcheck scan -f requirements.txt \
--with-osv \
--resume-state .trustcheck/scan-state.json
Every completed target is checkpointed atomically. A restarted scan restores successful reports and retries failed targets. State is accepted only when its fingerprint matches the source digest, resolved targets, policy, indexes, advisory options, and enabled plugins. Stale or mismatched state fails closed.
Plugins¶
Experimental
Third-party Trustcheck plugins remain explicitly opt-in. Keep plugin loading disabled unless the plugin distribution and installed-code digest are explicitly approved through a trusted key or digest allowlist.
Plugins are disabled by default and require an explicit allowlist. Trusted
plugin execution requires a signed trustcheck-plugin.json statement that
binds the name, kind, entry point, API version, plugin protocol version,
distribution name and version, canonical RECORD digest, canonical installed
content digest, declared dependencies, declared capabilities, and
configuration schema digest. Trustcheck verifies the
signature, installed files against wheel RECORD, and a configured external
trust root before importing code in a spawned, resource-bounded worker.
Self-signed plugin metadata alone is rejected. Execution status, timing, and
whether the resource-bounded worker was used are included in report
diagnostics.
Before importing plugin code, the resource-bounded worker redirects bytecode
lookup to a new private empty cache, disables bytecode writes, and invalidates
import caches. Adjacent pip-generated __pycache__ files are therefore not a
source-bypass for signed plugins. isolate = false loads plugins in the main
interpreter and cannot provide the same source-binding guarantee.
The worker is a plugin integrity and IPC boundary, not a complete operating system sandbox. A trusted plugin still runs Python code with the worker process's filesystem, network, and subprocess permissions unless the host environment applies additional controls. Do not treat declared plugin capabilities as enforced resource policy; Trustcheck rejects filesystem, network, and subprocess capability claims until those permissions can be mapped to real isolation. A future hardened mode should use OS controls such as Bubblewrap on Linux, a locked container fallback, capability-specific filesystem mounts, network disabled unless explicitly permitted, subprocess restrictions, and platform-specific warnings where equivalent isolation is not available.
Worker IPC uses plugin protocol version 1 over
multiprocessing.Pipe.send_bytes() and recv_bytes(). The parent and worker
exchange UTF-8 JSON bytes only; plugin-controlled Python objects are never
received with Pipe.recv() or unpickled in the parent process. Responses use
this envelope:
{
"plugin_protocol_version": "1",
"request_id": "request-id",
"ok": true,
"result": {}
}
The parent validates the envelope, protocol version, request id, JSON shape,
message byte size, nested depth, list and mapping length, and string length.
The worker returns plain data, and the parent reconstructs only trusted
Trustcheck models: vulnerability records, artifact findings, policy
violations, index projects and files, dependency-confusion findings, byte
payloads, and plugin errors. Unknown fields and unsupported return types fail
closed. The packaged schemas are
trustcheck/plugin_schemas/plugin-statement-2.json,
trustcheck/plugin_schemas/plugin-ipc-request-1.json and
trustcheck/plugin_schemas/plugin-ipc-response-1.json.
trustcheck scan -f requirements.txt \
--plugin advisory:company-osv \
--plugin policy:company-policy \
--plugin-config trustcheck-plugins.json
Supported entry-point groups:
| Kind | Entry-point group | Purpose |
|---|---|---|
| Advisory | trustcheck.advisory_sources |
Return normalized vulnerability records |
| Index | trustcheck.indexes |
Route custom index schemes and repositories |
| Artifact | trustcheck.artifact_analyzers |
Add heuristic artifact findings |
| Policy | trustcheck.policy_rules |
Add enforced policy violations |
| Renderer | trustcheck.renderers |
Add output formats |
Example registration:
[project.entry-points."trustcheck.policy_rules"]
company-policy = "company_trustcheck:CompanyPolicy"
Plugin objects declare a stable name. Configuration is a JSON object keyed
by that name. Plugin API version 1 uses the public protocols exported by
trustcheck; resource-bounded workers reject arbitrary custom result objects.
The distribution includes trustcheck-plugin.json:
{
"schema": "urn:trustcheck:plugin-manifest:2",
"manifest": {
"schema": "urn:trustcheck:plugin-statement:2",
"name": "company-policy",
"kind": "policy",
"entry_point": "company_trustcheck:CompanyPolicy",
"api_version": "1",
"distribution": "company-trustcheck-policy",
"distribution_version": "1.2.3",
"wheel_sha256": "64 lowercase hex characters",
"record_sha256": "64 lowercase hex characters",
"configuration_schema_sha256": "64 lowercase hex characters",
"protocol_version": "1",
"capabilities": ["evaluate"],
"dependencies": []
},
"public_key": "-----BEGIN PUBLIC KEY-----...",
"signature": "base64-rsa-pkcs1v15-sha256",
"configuration_schema": {
"type": "object",
"additionalProperties": false
}
}
The signature covers canonical compact JSON for manifest with sorted keys.
The statement file itself and RECORD are metadata and are excluded from the
canonical installed-content digest; every other signed file must have a
matching sha256 RECORD entry. Trustcheck canonicalizes RECORD before
digesting it so row order, pip-generated __pycache__ bytecode rows, and
installer metadata such as INSTALLER, REQUESTED, and direct_url.json do
not make a correctly signed wheel unverifiable after a real pip install.
Modifying plugin code, dependencies, the canonical RECORD rows, the declared
configuration schema, or declared capabilities fails closed. Wheels declaring
console_scripts or gui_scripts are rejected by the manifest signing command
until installer-generated script paths can be bound portably across platforms.
The earlier requires_network, requires_filesystem, and
requires_subprocess statement fields are rejected until Trustcheck has
enforcement that can turn those declarations into real sandbox policy.
Use the plugin-manifest helper to create and validate v2 metadata instead of hand-building the digests:
trustcheck plugin-manifest init dist/company_policy-1.2.3-py3-none-any.whl
trustcheck plugin-manifest sign dist/company_policy-1.2.3-py3-none-any.whl \
--key plugin-signing-key.pem
trustcheck plugin-manifest verify dist/company_policy-1.2.3-py3-none-any.whl
trustcheck plugin-manifest fingerprint plugin-signing-key.pub.pem
sign rewrites the wheel RECORD so the trustcheck-plugin.json entry is
unhashed, computes the final installed-content and RECORD digests, signs the
canonical statement with RSA-PKCS1v15-SHA256, inserts the envelope, and
revalidates the resulting wheel with the same verifier used at plugin load
time.
Plugin Manifest Migration¶
Plugin API version 1 remains the runtime method contract for plugin objects.
The signed manifest and statement formats are version 2 because they bind
additional security-critical data: distribution identity, distribution version,
canonical RECORD digest, canonical installed-content digest, configuration
schema digest, IPC protocol version, capabilities, and dependencies.
Legacy manifest v1 files that only identify the name, kind, entry point, and
API version are rejected with a migration error. Regenerate
trustcheck-plugin.json with the v2 statement fields and re-sign it with an
approved key before enabling the plugin.
Configure one trust-policy mode in _trustcheck:
| Mode | Required trust root |
|---|---|
trusted-key |
trusted_signers: SHA-256 fingerprints of approved public keys |
allowlisted-digest |
trusted_wheel_sha256: canonical installed-content digests |
organization-policy |
Any configured trusted_signers or trusted_wheel_sha256 root |
disabled |
Disables signed-plugin enforcement only when require_signed=false |
Plugin statements may not claim sigstore_identity or sigstore_issuer.
Sigstore identity trust is intentionally unsupported until Trustcheck verifies
a real Sigstore bundle or attestation, including the certificate chain, Fulcio
root, transparency-log inclusion where applicable, certificate identity, OIDC
issuer, artifact or installed-content digest, and transparency-log inclusion proof
before considering the identity verified.
Example plugin configuration:
{
"_trustcheck": {
"allowlist": ["policy:company-policy"],
"trust_policy_mode": "trusted-key",
"trusted_signers": [
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
]
},
"company-policy": {}
}
Exceptions, timeouts, signature failures, trust-root failures, and contract violations fail closed.
The GitHub Action exposes enable-plugins, plugins, and plugin-config.
The workflow must install plugin distributions before invoking the composite
action.