Skip to content

Security: kdeldycke/repomatic

Security

docs/security.md

{octicon}shield-lock Security

Supply chain security

repomatic implements most of the practices described in Astral's Open Source Security at Astral post, baked into a drop-in setup that any maintainer can inherit by pointing their workflows at the reusable callers.

Astral practice How repomatic covers it
Ban dangerous triggers (pull_request_target, workflow_run) The lint-workflow-security job runs zizmor on every push: see .github/workflows/lint.yaml
Minimal workflow permissions check_workflow_permissions parses every workflow file and warns when a custom-step workflow omits the top-level permissions key
Pinned actions All uses: refs pinned to full commit SHAs (with the semver tag preserved as a trailing comment) via Renovate's helpers:pinGitHubActionDigestsToSemver preset: see renovate.json5
No force-pushes to main check_branch_ruleset_on_default verifies an active branch ruleset exists, and the setup guide walks users through creating one
Immutable release tags check_immutable_releases verifies GitHub immutable releases is enabled, and the release workflow rewrites @main refs to @vX.Y.Z during freeze: see tagged workflow URLs
Dependency cooldowns Renovate stabilization windows (minimumReleaseAge) and uv --exclude-newer, with a per-package escape hatch for CVE fixes: see renovate.json5 and Renovate cooldowns
Trusted Publishing PyPI uploads via uv publish with no long-lived token: see the publish-pypi job in .github/workflows/release.yaml
Cryptographic attestations Every binary and wheel is attested to the workflow run that built it via attest-build-provenance: see the Generate build attestations steps in .github/workflows/release.yaml
Checksums in installer scripts The update-checksums CLI command regenerates SHA-256 checksums on every release, invoked from .github/workflows/renovate.yaml when upstream action versions change
Fork PR approval policy check_fork_pr_approval_policy warns when the policy is weaker than first_time_contributors, and the setup guide ships a pre-filled gh api one-liner to fix it

Warning

Known gap: multi-person release approval. Astral gates releases behind a dedicated GitHub deployment environment with required reviewers, so that a single compromised account cannot publish. repomatic does not enforce this, but if the repository has multiple maintainers, I recommend adding an environment: release key to the publish-pypi and create-release jobs in a downstream caller workflow and configuring required reviewers on that environment in repo settings.

Third-party action minimization

Every third-party GitHub Action executes with access to GITHUB_TOKEN and repository secrets. Each action is a trust delegation: you depend on the maintainer's security practices, their CI pipeline, and their transitive dependencies. A compromised action can steal secrets, inject code into builds, or tamper with releases.

repomatic has systematically eliminated 18 third-party actions since late 2025, replacing them with internal CLI commands, SHA-256-verified binary downloads, and runner built-in tools:

Removed action Replacement Strategy
calibreapp/image-actions repomatic format-images Internal CLI
crazy-max/ghaction-virustotal repomatic scan-virustotal Internal CLI
AndreasAugustin/actions-template-sync repomatic sync-awesome-template Internal CLI
JasonEtco/is-sponsor-label-action repomatic sponsor-label Internal CLI
lycheeverse/lychee-action repomatic run lychee Direct binary + SHA-256
crate-ci/typos repomatic run typos Direct binary + SHA-256
biomejs/setup-biome repomatic run biome Direct binary + SHA-256
gitleaks/gitleaks-action repomatic run gitleaks Direct binary + SHA-256
julb/action-manage-label repomatic run labelmaker Direct binary + SHA-256
taiki-e/install-action Direct curl + checksum Direct binary + SHA-256
softprops/action-gh-release gh release create Runner built-in
actions/github-script Bash + gh CLI Runner built-in
actions-rust-lang/setup-rust-toolchain Runner built-in Rust Runner built-in
actions/setup-python astral-sh/setup-uv Consolidated
peaceiris/actions-gh-pages actions/deploy-pages First-party replacement
codecov/codecov-action codecov-cli via uvx Pinned CLI
codecov/test-results-action codecov-cli via uvx Pinned CLI
GitHubSecurityLab/actions-permissions Explicit permissions: key Removed entirely

The remaining third-party actions (5 of 14 total) are:

Action Purpose
astral-sh/setup-uv Core toolchain: installs uv
peter-evans/create-pull-request Creates autofix PRs
dessant/lock-threads Locks inactive issues
renovatebot/github-action Dependency updates
crazy-max/ghaction-dump-context Debug diagnostics (no secrets access)

Replacement strategies, ordered from most to least isolated:

  1. Internal CLI: the operation runs inside repomatic Python code with no external process.
  2. Direct binary download: checksummed binary fetched from a GitHub release URL, no action code path involved.
  3. Runner built-in: uses tools pre-installed on the GitHub Actions runner (gh, Rust toolchain).
  4. First-party replacement: swaps a community action for an official actions/* equivalent maintained by GitHub.

Ruff consolidation

Eight separate Python linters and formatters have been absorbed into ruff, eliminating eight runtime or dev dependencies:

Removed tool What it did Replaced
pylint Static analysis and linting Feb 2023
pydocstyle Docstring convention enforcement Feb 2023
pycln Unused import removal Feb 2023
pyupgrade Python syntax modernization Feb 2023
isort Import sorting Feb 2023
black Code formatting Sep 2023
docformatter Docstring formatting Jan 2024
blacken-docs Python formatting in Markdown code blocks Feb 2026

The mdformat-black plugin was also swapped for mdformat-ruff (Aug 2024): same dependency count, but aligns the Markdown pipeline with ruff's formatting rules.

autopep8 is the only legacy formatter still in use: it handles long-line comment wrapping that ruff does not yet cover.

uv consolidation

Five separate packaging and install tools have been absorbed into uv, which now handles dependency management, builds, publishing, auditing, and Python version installation:

Removed tool What it did Replaced
poetry Dependency management, lock files, virtual environments Jun 2024
build / python -m build Package building (wheels and sdists) Sep 2024
twine PyPI uploads Jan 2025
check-wheel-contents Wheel validation Jan 2025
pip-audit Vulnerability scanning Mar 2026

uv also consolidated command-line usage that previously required separate tools: pip install became uv pip install / uv sync, pipx became uvx, and actions/setup-python was replaced by astral-sh/setup-uv (counted in the action minimization table above).

Two other Python packages were eliminated outside the ruff/uv consolidations: pipdeptree (replaced by an internal deps-graph implementation) and gitignore-parser (replaced by py-walk).

Permissions and token

Several workflows need a REPOMATIC_PAT secret to create PRs that modify files in .github/workflows/ and to trigger downstream workflows. Without it, those jobs silently fall back to the default GITHUB_TOKEN, which lacks the required permissions.

After your first push, the setup-guide job automatically opens an issue with step-by-step instructions to create and configure the token.

Concurrency and cancellation

All workflows use a concurrency directive to prevent redundant runs and save CI resources. When a new commit is pushed, any in-progress workflow runs for the same branch or PR are automatically cancelled.

Workflows are grouped by:

  • Pull requests: {workflow-name}-{pr-number} — Multiple commits to the same PR cancel previous runs
  • Branch pushes: {workflow-name}-{branch-ref} — Multiple pushes to the same branch cancel previous runs

release.yaml uses a stronger protection: release commits get a unique concurrency group based on the commit SHA, so they can never be cancelled. This ensures tagging, PyPI publishing, and GitHub release creation complete successfully.

Additionally, cancel-runs.yaml actively cancels in-progress and queued runs when a PR is closed. This complements passive concurrency groups, which only trigger cancellation when a new run enters the same group — closing a PR doesn't produce such an event.

Tip

For implementation details on how concurrency groups are computed and why release.yaml needs special handling, see the {mod}repomatic.github.actions module docstring.

AV false-positive submissions

Compiled Python binaries (built with Nuitka --onefile) are frequently flagged as malicious by heuristic AV engines. The onefile packaging technique (self-extracting archive with embedded Python runtime) triggers generic "packed/suspicious" signatures. This is a known issue across the Nuitka ecosystem.

The scan-virustotal job in release.yaml uploads all compiled binaries to VirusTotal on every release. This seeds AV vendor databases to reduce false positive rates for downstream distributors (Chocolatey, Scoop, etc.).

When a release is flagged, the /av-false-positive skill generates per-vendor submission files with pre-written text and form field mappings. The vendor details below document the process for manual reference.

Vendor portals

Vendor Engines covered Portal Format Turnaround
Microsoft Microsoft WDSI file submission One file per form, 1900 char limit on additional info Fastest
BitDefender BitDefender, ALYac, Arcabit, Emsisoft, GData, MicroWorld-eScan, VIPRE bitdefender.com/submit One file per form, screenshot mandatory Fast
ESET ESET-NOD32 Email to samples@eset.com Single email, password-protected ZIP (infected), ~24 MB limit Reliable
Symantec Symantec symsubmit.symantec.com Hash submission only (no .exe/.bin upload), one hash per form, 5000 char limit 3-7 business days
Avast/AVG Avast, AVG avast.com/submit-a-sample One file per form, shared engine Medium
Sophos Sophos sophos.com filesubmission One file per form, 25 MB max per submission Up to 15 business days

Submission priority

Submit in this order to maximize impact:

  1. Microsoft: most influential engine. ML detections (Sabsik, Wacatac) have the broadest downstream effect.
  2. BitDefender: powers ~6 downstream vendor engines. Highest detection-removal-per-submission ratio.
  3. ESET: email-based channel with no portal dependency. The most reliable submission path.
  4. Symantec: ML detections (ML.Attribute.*) may take longer to process.
  5. Avast/AVG: shared engine, so one submission covers both.
  6. Sophos: PUA detections require justification of the software's legitimate purpose.

Submission content

Every false-positive submission should include:

  • The binary's VirusTotal report link.
  • VirusTotal links for the clean .whl and .tar.gz source distributions (as comparison evidence).
  • The GitHub release link and direct download URL for the binary.
  • Project homepage and PyPI URL.
  • License from pyproject.toml.
  • Reference to any prior false-positive issue in the repository.

All submission text should mention that the binary is compiled with Nuitka --onefile from an open-source project.

Known portal issues

  • Microsoft: CORS errors or stuck progress modals during upload (auth session expiring). Workaround: sign out, clear cookies for microsoft.com, sign back in, submit immediately.
  • BitDefender: form sometimes returns "Your request could not be registered!" with no details. Retry later.
  • Avast: form sometimes returns "An internal error occurred while sending the form." Retry later.

There aren’t any published security advisories