trustcheck 2.2.0__tar.gz → 2.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {trustcheck-2.2.0 → trustcheck-2.2.2}/.github/workflows/ci.yml +11 -4
- {trustcheck-2.2.0 → trustcheck-2.2.2}/.github/workflows/publish.yml +81 -3
- {trustcheck-2.2.0 → trustcheck-2.2.2}/MANIFEST.in +1 -0
- {trustcheck-2.2.0/src/trustcheck.egg-info → trustcheck-2.2.2}/PKG-INFO +16 -15
- {trustcheck-2.2.0 → trustcheck-2.2.2}/README.md +15 -14
- trustcheck-2.2.2/scripts/export_homebrew_tap.py +312 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/_version.py +2 -2
- {trustcheck-2.2.0 → trustcheck-2.2.2/src/trustcheck.egg-info}/PKG-INFO +16 -15
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck.egg-info/SOURCES.txt +2 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_ci_workflow.py +5 -0
- trustcheck-2.2.2/tests/test_homebrew_tap_export.py +184 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_release_readiness.py +17 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/.dockerignore +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/.gitattributes +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/.github/CODEOWNERS +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/.github/ISSUE_TEMPLATE/general.yml +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/.github/dependabot.yml +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/.github/trustcheck-action-fail-policy.json +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/.github/workflows/acceptance-matrix.yml +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/.github/workflows/action-integration.yml +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/.github/workflows/bandit.yml +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/.github/workflows/benchmarks.yml +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/.github/workflows/binary-security.yml +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/.github/workflows/codeql.yml +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/.github/workflows/docs.yml +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/.github/workflows/fuzz.yml +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/.github/workflows/live-integration.yml +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/.github/workflows/mutation.yml +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/.github/workflows/plagiarism-scan.yml +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/.github/workflows/post-release-parity.yml +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/.github/workflows/sarif-integration.yml +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/.github/workflows/semgrep.yml +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/.github/workflows/source-build.yml +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/.gitignore +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/.pre-commit-hooks.yaml +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/CHANGELOG.md +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/CONTRIBUTING.md +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/Dockerfile +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/LICENSE +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/SECURITY.md +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/action.yml +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/benchmarks/README.md +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/benchmarks/benchmark_against_pip_audit.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/benchmarks/corpus/corpus.json +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/benchmarks/corpus/malicious-calibration.json +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/benchmarks/corpus/pdm.lock +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/benchmarks/corpus/poetry.lock +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/benchmarks/corpus/pylock.toml +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/benchmarks/corpus/requirements-hashed.txt +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/benchmarks/corpus/requirements-main.txt +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/benchmarks/corpus/requirements-malformed.txt +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/benchmarks/corpus/requirements-markers-extras.txt +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/benchmarks/corpus/requirements-private-index.txt +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/benchmarks/corpus/requirements-profiles.txt +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/benchmarks/corpus/requirements-resolution.txt +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/benchmarks/corpus/requirements-vcs-editable.txt +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/benchmarks/corpus/requirements.txt +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/benchmarks/corpus/truth-public-key.pem +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/benchmarks/corpus/truth.json +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/benchmarks/corpus/truth.json.sig +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/benchmarks/corpus/uv.lock +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/benchmarks/measure_command.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/benchmarks/results/benchmark-public-key.pem +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/benchmarks/results/latest.json +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/benchmarks/results/latest.json.sig +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/docs/assets/images/logo-bg-less.png +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/docs/assets/images/logo.png +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/docs/assets/javascripts/disable-search-shortcut.js +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/docs/changelog.md +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/docs/cli/configuration.md +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/docs/cli/exit-codes.md +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/docs/cli/index.md +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/docs/cli/policies.md +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/docs/getting-started/installation.md +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/docs/getting-started/quickstart.md +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/docs/guides/ci-integration.md +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/docs/index.md +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/docs/reference/benchmarks.md +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/docs/reference/compatibility.md +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/docs/reference/industry-formats.md +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/docs/reference/json-contract.md +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/docs/reference/malicious-package-detection.md +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/docs/reference/performance-extensibility.md +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/docs/reference/python-api.md +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/docs/reference/recommendations.md +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/docs/reference/remediation.md +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/docs/reference/trust-model.md +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/fuzz/README.md +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/fuzz/fuzz_artifacts.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/fuzz/fuzz_exports.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/fuzz/fuzz_indexes.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/fuzz/fuzz_lockfiles.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/fuzz/fuzz_provenance.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/fuzz/fuzz_requirements.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/mkdocs.yml +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/pyproject.toml +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/requirements/action.lock +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/requirements/ci.in +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/requirements/ci.lock +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/requirements/fuzz.in +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/requirements/fuzz.lock +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/requirements/runtime.in +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/requirements/runtime.lock +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/requirements/semgrep.in +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/requirements/semgrep.lock +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/scripts/acceptance_matrix.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/scripts/benchmark_signature.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/scripts/build_msix_layout.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/scripts/build_standalone.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/scripts/check_mutation_score.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/scripts/dependency_bounds.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/scripts/github_plagiarism_scan.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/scripts/smoke_test_distribution.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/scripts/trustcheck_binary.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/scripts/update_benchmark_table.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/scripts/update_coverage_badge.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/scripts/validate_sarif.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/scripts/verify_release_channels.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/scripts/verify_release_version.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/setup.cfg +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/snap/README.md +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/snap/gui/icon.png +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/snap/snapcraft.yaml +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/__init__.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/__main__.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/_resolver_guard.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/advisories.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/artifacts.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/attestations.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/cache.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/cli.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/cli_commands/__init__.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/cli_commands/context.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/cli_commands/diff.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/cli_commands/doctor.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/cli_commands/environment.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/cli_commands/impact.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/cli_commands/inspect.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/cli_commands/install.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/cli_commands/manifest.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/cli_commands/scan.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/cli_models.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/cli_render.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/cli_runtime.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/cli_targets.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/contract.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/diff.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/doctor.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/dynamic.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/export_models.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/export_xml.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/exports.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/github_action.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/impact.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/indexes.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/lockfiles.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/malicious.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/manifest.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/models.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/plugins.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/policy.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/pre_commit.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/provenance.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/py.typed +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/pypi.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/remediation.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/remediation_models.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/remediation_render.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/resolver.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/resume.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/schemas.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/service.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/service_state.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/service_urls.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/snapshots.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck/workspace.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck.egg-info/dependency_links.txt +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck.egg-info/entry_points.txt +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck.egg-info/requires.txt +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/src/trustcheck.egg-info/top_level.txt +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/_tmp/bad-scan.toml +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/_tmp/cache/5e491d79f8ba9e36d864ae50c690989677616cd509e5b99abb9272c8ad976435.json +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/_tmp/config_non_object.json +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/_tmp/empty-scan.toml +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/_tmp/empty-scan.txt +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/_tmp/invalid-scan.txt +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/_tmp/policy_non_object.json +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/_tmp/scan-poetry.toml +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/_tmp/scan-project.toml +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/fixtures/client_config.json +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/fixtures/policy_require_expected_repo.json +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/fixtures/requirements-vulnerable.txt +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/snapshots/contract_schema.json +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/snapshots/report_minimal.json +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/snapshots/report_verified.json +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_advisories.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_artifacts.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_attestations.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_benchmark_results.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_binary_security_workflow.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_cli.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_contract.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_dependency_bounds.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_diff.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_docker_workflows.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_doctor.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_dynamic.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_edge_cases.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_exports.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_github_action.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_impact.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_indexes.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_install_command.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_integration_live.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_lockfiles.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_malicious.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_manifest.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_msix_packaging.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_mutation_score.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_performance_extensibility.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_plagiarism_scan.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_plugin_security.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_pre_commit.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_property_invariants.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_provenance.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_public_api.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_pypi.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_release_channels.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_release_executable.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_release_version.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_remediation.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_resolver.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_resolver_guard.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_sarif_validation.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_scan_profiles.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_service.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_snap_packaging.py +0 -0
- {trustcheck-2.2.0 → trustcheck-2.2.2}/tests/test_workspace.py +0 -0
|
@@ -379,13 +379,20 @@ jobs:
|
|
|
379
379
|
GH_TOKEN: ${{ github.token }}
|
|
380
380
|
run: |
|
|
381
381
|
publish_root="$(mktemp -d)"
|
|
382
|
-
cp coverage-artifacts/coverage-badge/coverage.svg "$publish_root/coverage.svg"
|
|
383
382
|
git -C "$publish_root" init
|
|
384
383
|
git -C "$publish_root" config user.name "github-actions[bot]"
|
|
385
384
|
git -C "$publish_root" config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
386
|
-
git -C "$publish_root" add coverage.svg
|
|
387
|
-
git -C "$publish_root" commit -m "Update coverage badge for ${GITHUB_SHA}"
|
|
388
|
-
git -C "$publish_root" branch -M coverage-badge
|
|
389
385
|
git -C "$publish_root" remote add origin \
|
|
390
386
|
"https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
|
|
387
|
+
git -C "$publish_root" fetch --depth=1 origin coverage-badge
|
|
388
|
+
if ! git -C "$publish_root" show FETCH_HEAD:PyPI.png > "$publish_root/PyPI.png"; then
|
|
389
|
+
printf '%s\n' \
|
|
390
|
+
"PyPI.png is missing from coverage-badge; refusing to replace the asset branch" \
|
|
391
|
+
"without it." >&2
|
|
392
|
+
exit 1
|
|
393
|
+
fi
|
|
394
|
+
cp coverage-artifacts/coverage-badge/coverage.svg "$publish_root/coverage.svg"
|
|
395
|
+
git -C "$publish_root" add PyPI.png coverage.svg
|
|
396
|
+
git -C "$publish_root" commit -m "Update coverage badge for ${GITHUB_SHA}"
|
|
397
|
+
git -C "$publish_root" branch -M coverage-badge
|
|
391
398
|
git -C "$publish_root" push --force origin coverage-badge
|
|
@@ -837,6 +837,84 @@ jobs:
|
|
|
837
837
|
packages-dir: dist/packages
|
|
838
838
|
verbose: true
|
|
839
839
|
|
|
840
|
+
publish-homebrew-tap:
|
|
841
|
+
needs: publish-pypi
|
|
842
|
+
runs-on: ubuntu-latest
|
|
843
|
+
|
|
844
|
+
steps:
|
|
845
|
+
- name: Check out tagged commit
|
|
846
|
+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
847
|
+
with:
|
|
848
|
+
ref: ${{ github.sha }}
|
|
849
|
+
persist-credentials: false
|
|
850
|
+
|
|
851
|
+
- name: Set up Python
|
|
852
|
+
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
|
853
|
+
with:
|
|
854
|
+
python-version: "3.12"
|
|
855
|
+
|
|
856
|
+
- name: Download build artifacts
|
|
857
|
+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
858
|
+
with:
|
|
859
|
+
name: dist-${{ github.sha }}
|
|
860
|
+
path: dist
|
|
861
|
+
|
|
862
|
+
- name: Export Homebrew tap pins
|
|
863
|
+
run: |
|
|
864
|
+
python scripts/export_homebrew_tap.py \
|
|
865
|
+
--runtime-lock requirements/runtime.lock \
|
|
866
|
+
--build-lock requirements/action.lock \
|
|
867
|
+
--checksums dist/SHA256SUMS.txt \
|
|
868
|
+
--output-dir homebrew-tap-export/trustcheck \
|
|
869
|
+
--tag "$GITHUB_REF_NAME" \
|
|
870
|
+
--source-repository "$GITHUB_REPOSITORY" \
|
|
871
|
+
--source-commit "$GITHUB_SHA" \
|
|
872
|
+
--extra-package setuptools-scm \
|
|
873
|
+
--extra-package wheel
|
|
874
|
+
|
|
875
|
+
- name: Require Homebrew tap token
|
|
876
|
+
env:
|
|
877
|
+
HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN || secrets.RELEASE_TOKEN }}
|
|
878
|
+
run: |
|
|
879
|
+
if [ -z "$HOMEBREW_TAP_TOKEN" ]; then
|
|
880
|
+
echo "Set HOMEBREW_TAP_TOKEN or RELEASE_TOKEN with contents:write access to Halfblood-Prince/homebrew-tap." >&2
|
|
881
|
+
exit 1
|
|
882
|
+
fi
|
|
883
|
+
|
|
884
|
+
- name: Check out Homebrew tap
|
|
885
|
+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
886
|
+
with:
|
|
887
|
+
repository: Halfblood-Prince/homebrew-tap
|
|
888
|
+
ref: main
|
|
889
|
+
token: ${{ secrets.HOMEBREW_TAP_TOKEN || secrets.RELEASE_TOKEN }}
|
|
890
|
+
path: homebrew-tap
|
|
891
|
+
persist-credentials: true
|
|
892
|
+
|
|
893
|
+
- name: Commit Homebrew tap pins
|
|
894
|
+
env:
|
|
895
|
+
RELEASE_TAG: ${{ github.ref_name }}
|
|
896
|
+
run: |
|
|
897
|
+
mkdir -p homebrew-tap/trustcheck
|
|
898
|
+
cp homebrew-tap-export/trustcheck/runtime.lock homebrew-tap/trustcheck/runtime.lock
|
|
899
|
+
cp homebrew-tap-export/trustcheck/resources.rb homebrew-tap/trustcheck/resources.rb
|
|
900
|
+
cp homebrew-tap-export/trustcheck/release.json homebrew-tap/trustcheck/release.json
|
|
901
|
+
|
|
902
|
+
git -C homebrew-tap config user.name "github-actions[bot]"
|
|
903
|
+
git -C homebrew-tap config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
904
|
+
git -C homebrew-tap add trustcheck
|
|
905
|
+
if git -C homebrew-tap diff --cached --quiet; then
|
|
906
|
+
echo "No Homebrew tap pin changes to commit."
|
|
907
|
+
exit 0
|
|
908
|
+
fi
|
|
909
|
+
git -C homebrew-tap commit -m "Update trustcheck Homebrew pins for ${RELEASE_TAG}"
|
|
910
|
+
git -C homebrew-tap push
|
|
911
|
+
{
|
|
912
|
+
echo "## Homebrew tap"
|
|
913
|
+
echo
|
|
914
|
+
echo "- Updated \`Halfblood-Prince/homebrew-tap\` trustcheck pin export for \`${RELEASE_TAG}\`."
|
|
915
|
+
echo "- Wrote \`trustcheck/runtime.lock\`, \`trustcheck/resources.rb\`, and \`trustcheck/release.json\`."
|
|
916
|
+
} >> "$GITHUB_STEP_SUMMARY"
|
|
917
|
+
|
|
840
918
|
publish-docker:
|
|
841
919
|
needs:
|
|
842
920
|
- coverage-build
|
|
@@ -935,9 +1013,9 @@ jobs:
|
|
|
935
1013
|
append_body: true
|
|
936
1014
|
body: |
|
|
937
1015
|
Published from immutable commit `${{ github.sha }}`.
|
|
938
|
-
The release workflow publishes PyPI, GitHub Action, Snap Store,
|
|
939
|
-
GHCR Docker distributions after shared tag
|
|
940
|
-
and coverage builds.
|
|
1016
|
+
The release workflow publishes PyPI, GitHub Action, Snap Store,
|
|
1017
|
+
Homebrew tap pins, and GHCR Docker distributions after shared tag
|
|
1018
|
+
verification, QA, matrix, and coverage builds.
|
|
941
1019
|
|
|
942
1020
|
Release artifacts:
|
|
943
1021
|
- `dist/*`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: trustcheck
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.2
|
|
4
4
|
Summary: Package trust and provenance verification for PyPI consumers.
|
|
5
5
|
License-Expression: LicenseRef-Trustcheck-Personal-Use
|
|
6
6
|
Project-URL: Repository, https://github.com/Halfblood-Prince/trustcheck
|
|
@@ -63,6 +63,7 @@ Dynamic: license-file
|
|
|
63
63
|
[](https://github.com/marketplace/actions/trustcheck-package-scanner)
|
|
64
64
|
|
|
65
65
|
[](https://snapcraft.io/trustcheck)
|
|
66
|
+
<a href="https://pypi.org/project/trustcheck/"><img src="https://raw.githubusercontent.com/Halfblood-Prince/trustcheck/coverage-badge/PyPI.png" alt="Get it from PyPI" height="55"></a>
|
|
66
67
|
|
|
67
68
|
`trustcheck` is a Python package and CLI for evaluating the trust posture of PyPI releases before they are installed, promoted, or approved.
|
|
68
69
|
|
|
@@ -81,6 +82,20 @@ Packages that publish no provenance are treated as needing review rather than as
|
|
|
81
82
|
| `manifest` | lock approved trust evidence |
|
|
82
83
|
| `impact` | prioritize by observed usage |
|
|
83
84
|
|
|
85
|
+
<!-- trustcheck-benchmark:start -->
|
|
86
|
+
## Latest benchmark
|
|
87
|
+
|
|
88
|
+
Generated `2026-07-04T12:38:12.871592+00:00` on Python `3.14.6` with `pip-audit 2.10.1`. Corpus `2026.06` contains 133 entries; this fixed-input `--no-deps` comparison covers 112 comparable package entries.
|
|
89
|
+
|
|
90
|
+
| Tool | Cold p50 | Warm p50 | Warm p95 | Peak RSS | Requests p50 | Recall |
|
|
91
|
+
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
|
|
92
|
+
| trustcheck scan --fast | 16.00 s | 14.20 s | 14.44 s | 78.0 MiB | unknown | 1 |
|
|
93
|
+
| pip-audit | 36.69 s | 38.51 s | 39.82 s | 75.6 MiB | unknown | 1 |
|
|
94
|
+
|
|
95
|
+
Alias-aware agreement: `1.0` across `105` compared packages and `263` matched advisories.
|
|
96
|
+
Resolver exact match: `True` (trustcheck `22`, pip-audit `22`).
|
|
97
|
+
<!-- trustcheck-benchmark:end -->
|
|
98
|
+
|
|
84
99
|
## What it checks
|
|
85
100
|
|
|
86
101
|
For a selected package version, `trustcheck` can:
|
|
@@ -141,20 +156,6 @@ artifact is scanned with ClamAV. Clean binaries, checksums, and scanner reports
|
|
|
141
156
|
are retained as workflow artifacts by
|
|
142
157
|
[Binary Security](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/binary-security.yml).
|
|
143
158
|
|
|
144
|
-
<!-- trustcheck-benchmark:start -->
|
|
145
|
-
## Latest benchmark
|
|
146
|
-
|
|
147
|
-
Generated `2026-07-04T12:38:12.871592+00:00` on Python `3.14.6` with `pip-audit 2.10.1`. Corpus `2026.06` contains 133 entries; this fixed-input `--no-deps` comparison covers 112 comparable package entries.
|
|
148
|
-
|
|
149
|
-
| Tool | Cold p50 | Warm p50 | Warm p95 | Peak RSS | Requests p50 | Recall |
|
|
150
|
-
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
|
|
151
|
-
| trustcheck scan --fast | 16.00 s | 14.20 s | 14.44 s | 78.0 MiB | unknown | 1 |
|
|
152
|
-
| pip-audit | 36.69 s | 38.51 s | 39.82 s | 75.6 MiB | unknown | 1 |
|
|
153
|
-
|
|
154
|
-
Alias-aware agreement: `1.0` across `105` compared packages and `263` matched advisories.
|
|
155
|
-
Resolver exact match: `True` (trustcheck `22`, pip-audit `22`).
|
|
156
|
-
<!-- trustcheck-benchmark:end -->
|
|
157
|
-
|
|
158
159
|
## Installation
|
|
159
160
|
|
|
160
161
|
Install from PyPI:
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
[](https://github.com/marketplace/actions/trustcheck-package-scanner)
|
|
23
23
|
|
|
24
24
|
[](https://snapcraft.io/trustcheck)
|
|
25
|
+
<a href="https://pypi.org/project/trustcheck/"><img src="https://raw.githubusercontent.com/Halfblood-Prince/trustcheck/coverage-badge/PyPI.png" alt="Get it from PyPI" height="55"></a>
|
|
25
26
|
|
|
26
27
|
`trustcheck` is a Python package and CLI for evaluating the trust posture of PyPI releases before they are installed, promoted, or approved.
|
|
27
28
|
|
|
@@ -40,6 +41,20 @@ Packages that publish no provenance are treated as needing review rather than as
|
|
|
40
41
|
| `manifest` | lock approved trust evidence |
|
|
41
42
|
| `impact` | prioritize by observed usage |
|
|
42
43
|
|
|
44
|
+
<!-- trustcheck-benchmark:start -->
|
|
45
|
+
## Latest benchmark
|
|
46
|
+
|
|
47
|
+
Generated `2026-07-04T12:38:12.871592+00:00` on Python `3.14.6` with `pip-audit 2.10.1`. Corpus `2026.06` contains 133 entries; this fixed-input `--no-deps` comparison covers 112 comparable package entries.
|
|
48
|
+
|
|
49
|
+
| Tool | Cold p50 | Warm p50 | Warm p95 | Peak RSS | Requests p50 | Recall |
|
|
50
|
+
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
|
|
51
|
+
| trustcheck scan --fast | 16.00 s | 14.20 s | 14.44 s | 78.0 MiB | unknown | 1 |
|
|
52
|
+
| pip-audit | 36.69 s | 38.51 s | 39.82 s | 75.6 MiB | unknown | 1 |
|
|
53
|
+
|
|
54
|
+
Alias-aware agreement: `1.0` across `105` compared packages and `263` matched advisories.
|
|
55
|
+
Resolver exact match: `True` (trustcheck `22`, pip-audit `22`).
|
|
56
|
+
<!-- trustcheck-benchmark:end -->
|
|
57
|
+
|
|
43
58
|
## What it checks
|
|
44
59
|
|
|
45
60
|
For a selected package version, `trustcheck` can:
|
|
@@ -100,20 +115,6 @@ artifact is scanned with ClamAV. Clean binaries, checksums, and scanner reports
|
|
|
100
115
|
are retained as workflow artifacts by
|
|
101
116
|
[Binary Security](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/binary-security.yml).
|
|
102
117
|
|
|
103
|
-
<!-- trustcheck-benchmark:start -->
|
|
104
|
-
## Latest benchmark
|
|
105
|
-
|
|
106
|
-
Generated `2026-07-04T12:38:12.871592+00:00` on Python `3.14.6` with `pip-audit 2.10.1`. Corpus `2026.06` contains 133 entries; this fixed-input `--no-deps` comparison covers 112 comparable package entries.
|
|
107
|
-
|
|
108
|
-
| Tool | Cold p50 | Warm p50 | Warm p95 | Peak RSS | Requests p50 | Recall |
|
|
109
|
-
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
|
|
110
|
-
| trustcheck scan --fast | 16.00 s | 14.20 s | 14.44 s | 78.0 MiB | unknown | 1 |
|
|
111
|
-
| pip-audit | 36.69 s | 38.51 s | 39.82 s | 75.6 MiB | unknown | 1 |
|
|
112
|
-
|
|
113
|
-
Alias-aware agreement: `1.0` across `105` compared packages and `263` matched advisories.
|
|
114
|
-
Resolver exact match: `True` (trustcheck `22`, pip-audit `22`).
|
|
115
|
-
<!-- trustcheck-benchmark:end -->
|
|
116
|
-
|
|
117
118
|
## Installation
|
|
118
119
|
|
|
119
120
|
Install from PyPI:
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import http.client
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
import shutil
|
|
8
|
+
import time
|
|
9
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
10
|
+
from dataclasses import asdict, dataclass
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from urllib.parse import quote
|
|
13
|
+
|
|
14
|
+
PACKAGE_LINE = re.compile(
|
|
15
|
+
r"^(?P<name>[A-Za-z0-9_.-]+)==(?P<version>[^\\\s;]+)(?:\s*;[^\\]+)?(?:\s*\\)?$"
|
|
16
|
+
)
|
|
17
|
+
HASH_LINE = re.compile(r"--hash=sha256:(?P<hash>[0-9a-f]{64})")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
FetchJson = Callable[[str, str], Mapping[str, object]]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True, slots=True)
|
|
24
|
+
class LockedPackage:
|
|
25
|
+
name: str
|
|
26
|
+
version: str
|
|
27
|
+
hashes: frozenset[str]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True, slots=True)
|
|
31
|
+
class Distribution:
|
|
32
|
+
filename: str
|
|
33
|
+
url: str
|
|
34
|
+
sha256: str
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True, slots=True)
|
|
38
|
+
class ResourcePin:
|
|
39
|
+
name: str
|
|
40
|
+
version: str
|
|
41
|
+
filename: str
|
|
42
|
+
url: str
|
|
43
|
+
sha256: str
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def canonical_name(name: str) -> str:
|
|
47
|
+
return re.sub(r"[-_.]+", "-", name).lower()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def parse_lockfile(path: Path) -> dict[str, LockedPackage]:
|
|
51
|
+
packages: dict[str, LockedPackage] = {}
|
|
52
|
+
current_name: str | None = None
|
|
53
|
+
current_version: str | None = None
|
|
54
|
+
current_hashes: set[str] = set()
|
|
55
|
+
|
|
56
|
+
def flush_current() -> None:
|
|
57
|
+
nonlocal current_name, current_version, current_hashes
|
|
58
|
+
if current_name is None or current_version is None:
|
|
59
|
+
return
|
|
60
|
+
if not current_hashes:
|
|
61
|
+
raise ValueError(f"{path}: {current_name}=={current_version} has no hashes")
|
|
62
|
+
packages[canonical_name(current_name)] = LockedPackage(
|
|
63
|
+
name=current_name,
|
|
64
|
+
version=current_version,
|
|
65
|
+
hashes=frozenset(current_hashes),
|
|
66
|
+
)
|
|
67
|
+
current_name = None
|
|
68
|
+
current_version = None
|
|
69
|
+
current_hashes = set()
|
|
70
|
+
|
|
71
|
+
for raw_line in path.read_text(encoding="utf-8").splitlines():
|
|
72
|
+
line = raw_line.strip()
|
|
73
|
+
package_match = PACKAGE_LINE.match(line)
|
|
74
|
+
if package_match is not None:
|
|
75
|
+
flush_current()
|
|
76
|
+
current_name = package_match.group("name")
|
|
77
|
+
current_version = package_match.group("version")
|
|
78
|
+
continue
|
|
79
|
+
if current_name is None:
|
|
80
|
+
continue
|
|
81
|
+
for hash_match in HASH_LINE.finditer(line):
|
|
82
|
+
current_hashes.add(hash_match.group("hash"))
|
|
83
|
+
flush_current()
|
|
84
|
+
if not packages:
|
|
85
|
+
raise ValueError(f"{path}: no pinned packages found")
|
|
86
|
+
return packages
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def parse_checksums(path: Path) -> dict[str, str]:
|
|
90
|
+
checksums: dict[str, str] = {}
|
|
91
|
+
for line in path.read_text(encoding="utf-8").splitlines():
|
|
92
|
+
stripped = line.strip()
|
|
93
|
+
if not stripped:
|
|
94
|
+
continue
|
|
95
|
+
digest, filename = stripped.split(maxsplit=1)
|
|
96
|
+
checksums[Path(filename).name] = digest.lower()
|
|
97
|
+
if not checksums:
|
|
98
|
+
raise ValueError(f"{path}: no checksums found")
|
|
99
|
+
return checksums
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def read_pypi_json(project: str, version: str) -> Mapping[str, object]:
|
|
103
|
+
encoded_project = quote(project, safe="")
|
|
104
|
+
encoded_version = quote(version, safe="")
|
|
105
|
+
path = f"/pypi/{encoded_project}/{encoded_version}/json"
|
|
106
|
+
last_error: Exception | None = None
|
|
107
|
+
for attempt in range(1, 7):
|
|
108
|
+
connection: http.client.HTTPSConnection | None = None
|
|
109
|
+
try:
|
|
110
|
+
# PyPI host is fixed, and Python 3.12+ verifies TLS certificates by default.
|
|
111
|
+
# nosemgrep
|
|
112
|
+
connection = http.client.HTTPSConnection("pypi.org", timeout=30)
|
|
113
|
+
connection.request(
|
|
114
|
+
"GET",
|
|
115
|
+
path,
|
|
116
|
+
headers={
|
|
117
|
+
"Accept": "application/json",
|
|
118
|
+
"User-Agent": "trustcheck-homebrew-tap-exporter",
|
|
119
|
+
},
|
|
120
|
+
)
|
|
121
|
+
response = connection.getresponse()
|
|
122
|
+
response_data = response.read()
|
|
123
|
+
if response.status >= 400:
|
|
124
|
+
raise ValueError(
|
|
125
|
+
f"{project}=={version}: PyPI returned HTTP {response.status}"
|
|
126
|
+
)
|
|
127
|
+
payload = json.loads(response_data.decode("utf-8"))
|
|
128
|
+
if not isinstance(payload, dict):
|
|
129
|
+
raise ValueError(f"{project}=={version}: PyPI returned non-object JSON")
|
|
130
|
+
return payload
|
|
131
|
+
except (OSError, http.client.HTTPException, ValueError) as exc:
|
|
132
|
+
last_error = exc
|
|
133
|
+
if attempt < 6:
|
|
134
|
+
time.sleep(10)
|
|
135
|
+
finally:
|
|
136
|
+
if connection is not None:
|
|
137
|
+
connection.close()
|
|
138
|
+
raise ValueError(f"Unable to read PyPI metadata for {project}=={version}") from last_error
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def fetch_sdist(
|
|
142
|
+
project: str,
|
|
143
|
+
version: str,
|
|
144
|
+
fetch_json: FetchJson = read_pypi_json,
|
|
145
|
+
) -> Distribution:
|
|
146
|
+
payload = fetch_json(project, version)
|
|
147
|
+
urls = payload.get("urls")
|
|
148
|
+
if not isinstance(urls, list):
|
|
149
|
+
raise ValueError(f"{project}=={version}: PyPI metadata has no urls array")
|
|
150
|
+
|
|
151
|
+
candidates: list[Distribution] = []
|
|
152
|
+
for item in urls:
|
|
153
|
+
if not isinstance(item, Mapping) or item.get("packagetype") != "sdist":
|
|
154
|
+
continue
|
|
155
|
+
digests = item.get("digests")
|
|
156
|
+
filename = item.get("filename")
|
|
157
|
+
url = item.get("url")
|
|
158
|
+
if not isinstance(digests, Mapping):
|
|
159
|
+
continue
|
|
160
|
+
sha256 = digests.get("sha256")
|
|
161
|
+
if (
|
|
162
|
+
isinstance(filename, str)
|
|
163
|
+
and isinstance(url, str)
|
|
164
|
+
and isinstance(sha256, str)
|
|
165
|
+
and re.fullmatch(r"[0-9a-f]{64}", sha256)
|
|
166
|
+
):
|
|
167
|
+
candidates.append(Distribution(filename=filename, url=url, sha256=sha256))
|
|
168
|
+
if not candidates:
|
|
169
|
+
raise ValueError(f"{project}=={version}: no PyPI sdist found")
|
|
170
|
+
return sorted(
|
|
171
|
+
candidates,
|
|
172
|
+
key=lambda item: (not item.filename.endswith(".tar.gz"), item.filename),
|
|
173
|
+
)[0]
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def render_homebrew_resources(resources: Sequence[ResourcePin]) -> str:
|
|
177
|
+
lines = [
|
|
178
|
+
"# This file is generated by scripts/export_homebrew_tap.py.",
|
|
179
|
+
"# Do not edit manually.",
|
|
180
|
+
"",
|
|
181
|
+
]
|
|
182
|
+
for resource in resources:
|
|
183
|
+
lines.extend(
|
|
184
|
+
[
|
|
185
|
+
f'resource "{resource.name}" do',
|
|
186
|
+
f' url "{resource.url}"',
|
|
187
|
+
f' sha256 "{resource.sha256}"',
|
|
188
|
+
"end",
|
|
189
|
+
"",
|
|
190
|
+
]
|
|
191
|
+
)
|
|
192
|
+
return "\n".join(lines)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def export_homebrew_tap(
|
|
196
|
+
*,
|
|
197
|
+
runtime_lock: Path,
|
|
198
|
+
build_lock: Path | None,
|
|
199
|
+
checksums: Path,
|
|
200
|
+
output_dir: Path,
|
|
201
|
+
tag: str,
|
|
202
|
+
source_repository: str,
|
|
203
|
+
source_commit: str,
|
|
204
|
+
package_name: str = "trustcheck",
|
|
205
|
+
extra_packages: Sequence[str] = (),
|
|
206
|
+
fetch_json: FetchJson = read_pypi_json,
|
|
207
|
+
) -> None:
|
|
208
|
+
version = tag.removeprefix("v")
|
|
209
|
+
runtime_packages = parse_lockfile(runtime_lock)
|
|
210
|
+
build_packages = parse_lockfile(build_lock) if build_lock is not None else {}
|
|
211
|
+
selected_packages = dict(runtime_packages)
|
|
212
|
+
|
|
213
|
+
for package in extra_packages:
|
|
214
|
+
key = canonical_name(package)
|
|
215
|
+
if key in selected_packages:
|
|
216
|
+
continue
|
|
217
|
+
try:
|
|
218
|
+
selected_packages[key] = build_packages[key]
|
|
219
|
+
except KeyError as exc:
|
|
220
|
+
raise ValueError(
|
|
221
|
+
f"extra package {package!r} is not pinned in {build_lock}"
|
|
222
|
+
) from exc
|
|
223
|
+
|
|
224
|
+
release_sdist = fetch_sdist(package_name, version, fetch_json)
|
|
225
|
+
release_checksums = parse_checksums(checksums)
|
|
226
|
+
expected_release_hash = release_checksums.get(release_sdist.filename)
|
|
227
|
+
if expected_release_hash != release_sdist.sha256:
|
|
228
|
+
raise ValueError(
|
|
229
|
+
f"{release_sdist.filename} PyPI sha256 does not match {checksums}: "
|
|
230
|
+
f"{release_sdist.sha256} != {expected_release_hash}"
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
resources: list[ResourcePin] = []
|
|
234
|
+
for package in sorted(
|
|
235
|
+
selected_packages.values(),
|
|
236
|
+
key=lambda item: canonical_name(item.name),
|
|
237
|
+
):
|
|
238
|
+
sdist = fetch_sdist(package.name, package.version, fetch_json)
|
|
239
|
+
if sdist.sha256 not in package.hashes:
|
|
240
|
+
raise ValueError(
|
|
241
|
+
f"{package.name}=={package.version} sdist hash is not pinned in lockfile: "
|
|
242
|
+
f"{sdist.sha256}"
|
|
243
|
+
)
|
|
244
|
+
resources.append(
|
|
245
|
+
ResourcePin(
|
|
246
|
+
name=canonical_name(package.name),
|
|
247
|
+
version=package.version,
|
|
248
|
+
filename=sdist.filename,
|
|
249
|
+
url=sdist.url,
|
|
250
|
+
sha256=sdist.sha256,
|
|
251
|
+
)
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
255
|
+
shutil.copyfile(runtime_lock, output_dir / "runtime.lock")
|
|
256
|
+
(output_dir / "resources.rb").write_text(
|
|
257
|
+
render_homebrew_resources(resources),
|
|
258
|
+
encoding="utf-8",
|
|
259
|
+
)
|
|
260
|
+
(output_dir / "release.json").write_text(
|
|
261
|
+
json.dumps(
|
|
262
|
+
{
|
|
263
|
+
"generated_by": "scripts/export_homebrew_tap.py",
|
|
264
|
+
"package": {
|
|
265
|
+
"name": package_name,
|
|
266
|
+
"version": version,
|
|
267
|
+
"tag": tag,
|
|
268
|
+
"source_repository": source_repository,
|
|
269
|
+
"source_commit": source_commit,
|
|
270
|
+
"sdist": asdict(release_sdist),
|
|
271
|
+
},
|
|
272
|
+
"resources": [asdict(resource) for resource in resources],
|
|
273
|
+
},
|
|
274
|
+
indent=2,
|
|
275
|
+
sort_keys=True,
|
|
276
|
+
)
|
|
277
|
+
+ "\n",
|
|
278
|
+
encoding="utf-8",
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
283
|
+
parser = argparse.ArgumentParser(
|
|
284
|
+
description="Export trustcheck release pins for the Homebrew tap."
|
|
285
|
+
)
|
|
286
|
+
parser.add_argument("--runtime-lock", type=Path, required=True)
|
|
287
|
+
parser.add_argument("--build-lock", type=Path)
|
|
288
|
+
parser.add_argument("--checksums", type=Path, required=True)
|
|
289
|
+
parser.add_argument("--output-dir", type=Path, required=True)
|
|
290
|
+
parser.add_argument("--tag", required=True)
|
|
291
|
+
parser.add_argument("--source-repository", required=True)
|
|
292
|
+
parser.add_argument("--source-commit", required=True)
|
|
293
|
+
parser.add_argument("--package-name", default="trustcheck")
|
|
294
|
+
parser.add_argument("--extra-package", action="append", default=[])
|
|
295
|
+
args = parser.parse_args(argv)
|
|
296
|
+
|
|
297
|
+
export_homebrew_tap(
|
|
298
|
+
runtime_lock=args.runtime_lock,
|
|
299
|
+
build_lock=args.build_lock,
|
|
300
|
+
checksums=args.checksums,
|
|
301
|
+
output_dir=args.output_dir,
|
|
302
|
+
tag=args.tag,
|
|
303
|
+
source_repository=args.source_repository,
|
|
304
|
+
source_commit=args.source_commit,
|
|
305
|
+
package_name=args.package_name,
|
|
306
|
+
extra_packages=args.extra_package,
|
|
307
|
+
)
|
|
308
|
+
return 0
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
if __name__ == "__main__":
|
|
312
|
+
raise SystemExit(main())
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = 'v2.2.
|
|
22
|
-
__version_tuple__ = version_tuple = (2, 2,
|
|
21
|
+
__version__ = version = 'v2.2.2'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 2, 2)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: trustcheck
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.2
|
|
4
4
|
Summary: Package trust and provenance verification for PyPI consumers.
|
|
5
5
|
License-Expression: LicenseRef-Trustcheck-Personal-Use
|
|
6
6
|
Project-URL: Repository, https://github.com/Halfblood-Prince/trustcheck
|
|
@@ -63,6 +63,7 @@ Dynamic: license-file
|
|
|
63
63
|
[](https://github.com/marketplace/actions/trustcheck-package-scanner)
|
|
64
64
|
|
|
65
65
|
[](https://snapcraft.io/trustcheck)
|
|
66
|
+
<a href="https://pypi.org/project/trustcheck/"><img src="https://raw.githubusercontent.com/Halfblood-Prince/trustcheck/coverage-badge/PyPI.png" alt="Get it from PyPI" height="55"></a>
|
|
66
67
|
|
|
67
68
|
`trustcheck` is a Python package and CLI for evaluating the trust posture of PyPI releases before they are installed, promoted, or approved.
|
|
68
69
|
|
|
@@ -81,6 +82,20 @@ Packages that publish no provenance are treated as needing review rather than as
|
|
|
81
82
|
| `manifest` | lock approved trust evidence |
|
|
82
83
|
| `impact` | prioritize by observed usage |
|
|
83
84
|
|
|
85
|
+
<!-- trustcheck-benchmark:start -->
|
|
86
|
+
## Latest benchmark
|
|
87
|
+
|
|
88
|
+
Generated `2026-07-04T12:38:12.871592+00:00` on Python `3.14.6` with `pip-audit 2.10.1`. Corpus `2026.06` contains 133 entries; this fixed-input `--no-deps` comparison covers 112 comparable package entries.
|
|
89
|
+
|
|
90
|
+
| Tool | Cold p50 | Warm p50 | Warm p95 | Peak RSS | Requests p50 | Recall |
|
|
91
|
+
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
|
|
92
|
+
| trustcheck scan --fast | 16.00 s | 14.20 s | 14.44 s | 78.0 MiB | unknown | 1 |
|
|
93
|
+
| pip-audit | 36.69 s | 38.51 s | 39.82 s | 75.6 MiB | unknown | 1 |
|
|
94
|
+
|
|
95
|
+
Alias-aware agreement: `1.0` across `105` compared packages and `263` matched advisories.
|
|
96
|
+
Resolver exact match: `True` (trustcheck `22`, pip-audit `22`).
|
|
97
|
+
<!-- trustcheck-benchmark:end -->
|
|
98
|
+
|
|
84
99
|
## What it checks
|
|
85
100
|
|
|
86
101
|
For a selected package version, `trustcheck` can:
|
|
@@ -141,20 +156,6 @@ artifact is scanned with ClamAV. Clean binaries, checksums, and scanner reports
|
|
|
141
156
|
are retained as workflow artifacts by
|
|
142
157
|
[Binary Security](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/binary-security.yml).
|
|
143
158
|
|
|
144
|
-
<!-- trustcheck-benchmark:start -->
|
|
145
|
-
## Latest benchmark
|
|
146
|
-
|
|
147
|
-
Generated `2026-07-04T12:38:12.871592+00:00` on Python `3.14.6` with `pip-audit 2.10.1`. Corpus `2026.06` contains 133 entries; this fixed-input `--no-deps` comparison covers 112 comparable package entries.
|
|
148
|
-
|
|
149
|
-
| Tool | Cold p50 | Warm p50 | Warm p95 | Peak RSS | Requests p50 | Recall |
|
|
150
|
-
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
|
|
151
|
-
| trustcheck scan --fast | 16.00 s | 14.20 s | 14.44 s | 78.0 MiB | unknown | 1 |
|
|
152
|
-
| pip-audit | 36.69 s | 38.51 s | 39.82 s | 75.6 MiB | unknown | 1 |
|
|
153
|
-
|
|
154
|
-
Alias-aware agreement: `1.0` across `105` compared packages and `263` matched advisories.
|
|
155
|
-
Resolver exact match: `True` (trustcheck `22`, pip-audit `22`).
|
|
156
|
-
<!-- trustcheck-benchmark:end -->
|
|
157
|
-
|
|
158
159
|
## Installation
|
|
159
160
|
|
|
160
161
|
Install from PyPI:
|
|
@@ -101,6 +101,7 @@ scripts/build_msix_layout.py
|
|
|
101
101
|
scripts/build_standalone.py
|
|
102
102
|
scripts/check_mutation_score.py
|
|
103
103
|
scripts/dependency_bounds.py
|
|
104
|
+
scripts/export_homebrew_tap.py
|
|
104
105
|
scripts/github_plagiarism_scan.py
|
|
105
106
|
scripts/smoke_test_distribution.py
|
|
106
107
|
scripts/trustcheck_binary.py
|
|
@@ -188,6 +189,7 @@ tests/test_dynamic.py
|
|
|
188
189
|
tests/test_edge_cases.py
|
|
189
190
|
tests/test_exports.py
|
|
190
191
|
tests/test_github_action.py
|
|
192
|
+
tests/test_homebrew_tap_export.py
|
|
191
193
|
tests/test_impact.py
|
|
192
194
|
tests/test_indexes.py
|
|
193
195
|
tests/test_install_command.py
|
|
@@ -137,6 +137,11 @@ class CoverageBadgeWorkflowTests(unittest.TestCase):
|
|
|
137
137
|
self.assertIn("github.event_name == 'push'", workflow)
|
|
138
138
|
self.assertIn("group: coverage-badge", workflow)
|
|
139
139
|
self.assertIn("cancel-in-progress: true", workflow)
|
|
140
|
+
self.assertIn("git -C \"$publish_root\" fetch --depth=1 origin coverage-badge", workflow)
|
|
141
|
+
self.assertIn("show FETCH_HEAD:PyPI.png", workflow)
|
|
142
|
+
self.assertIn("refusing to replace the asset branch", workflow)
|
|
143
|
+
self.assertIn("\"without it.\"", workflow)
|
|
144
|
+
self.assertIn("git -C \"$publish_root\" add PyPI.png coverage.svg", workflow)
|
|
140
145
|
self.assertIn("git -C \"$publish_root\" push --force origin coverage-badge", workflow)
|
|
141
146
|
self.assertNotIn("git diff --exit-code -- docs/assets/images/coverage.svg", workflow)
|
|
142
147
|
|