Trust Boundary & Security Model
Relevant source files
The following files were used as context for generating this wiki page:
Introduction
The numan-plugins repository serves as a CI build and sign pipeline feeder for the official numan registry. Its primary security purpose is to provide a verifiable path from source-only Rust plugins to signed, hash-pinned binary artifacts. The architecture is designed to maintain a strict separation of concerns, where this repository builds and hosts binaries but never possesses the signing keys required for final registry integration.
The security model relies on immutable upstream pins, automated provenance verification, and a "draft-then-finalize" release transaction pattern. By offloading cryptographic signing to the numan-registry repository, numan-plugins minimizes the attack surface related to secret management while ensuring binary integrity through deterministic packaging and strict CI gates.
Sources: README.md:1-24, REVIEW.md:16-24
Core Security Invariants
The project operates under a set of non-negotiable architectural invariants designed to prevent unauthorized releases and maintain the integrity of the trust chain.
- Builds Only, Never Signs: This repository is restricted to hosting and building binaries. Ed25519 signing and the official trust root are isolated in the
numan-registryrepository. - No Private Keys: The repository must never contain signing keys, PEM private material, or registry secrets.
- Immutable Upstream Pins: All
active[]entries in themanifest.jsonmust pin human-facing tags to immutablesource_commitSHAs. - Asset Immutability: Existing release tags and assets are immutable. Any changes require a new package version or a build revision; overwriting is strictly refused.
- Manual Publication: Release workflows are triggered only via manual
workflow_dispatchwith a non-emptyonly=package list. Standard pushes and pull requests are restricted to validation only.
Sources: REVIEW.md:16-30, README.md:38-42
Trust Boundary Architecture
The following diagram illustrates the separation of duties between the build pipeline and the registry trust root.
This diagram shows that numan-plugins is responsible for the artifact creation and hosting, while numan-registry performs the intake, hashing, and signing.
Sources: README.md:17-30, REVIEW.md:16-18
Release Transaction & Safety
To ensure no "half-published" or tampered releases reach the public, the project implements a multi-stage release transaction script.
Release Workflow Stages
- Claim: The script creates a repository tag and a matching draft release associated with a specific workflow commit.
- Upload: Binary assets are uploaded to the claimed draft using a specific release ID to prevent "softprops" from creating unintentional duplicate drafts.
- Finalize: The system verifies the draft's ownership and confirms that the asset set (size and SHA256) matches the local build exactly before making the release public.
- Cleanup: If any stage fails, the script deletes the still-draft release and the associated tag.
Sources: scripts/release_transaction.py:53-159, README.md:43-47
Implementation of Safety Checks
The system utilizes several specialized scripts to enforce safety at the CI level:
| Script | Security Function |
|---|---|
validate_manifest.py | Ensures all active plugins have required metadata and valid 40-character commit SHAs. |
ensure_release_absent.py | Fails closed if a Git tag or release already exists, preventing accidental overwrites of immutable assets. |
test_workflow_safety.py | A test suite that verifies GitHub Action refs are pinned to commit SHAs and ensures no shell interpolation of secrets occurs. |
release_transaction.py | Manages the atomic transition from draft to public release with hash verification. |
Sources: scripts/validate_manifest.py:18-30, scripts/ensure_release_absent.py:18-50, scripts/test_workflow_safety.py:73-85, scripts/release_transaction.py:1-15
Provenance Verification
Provenance is maintained by mapping human-facing tags to specific, immutable Git commit SHAs in the upstream repository.
Tag-to-Commit Resolution
The scripts/validate_manifest.py script includes a resolve_tag function that uses git ls-remote to verify that a tag actually points to the declared source_commit. This prevents "tag sliding" or malicious updates to upstream repositories from silently entering the build pipeline.
def verify_upstream(entries: list[dict]) -> None:
"""Require every entry's upstream tag to match its immutable source commit."""
for entry in entries:
actual = resolve_tag(entry["repo"], entry["tag"])
expected = entry["source_commit"]
if actual != expected:
raise ValueError(
f"{entry['name']}: {entry['tag']} resolves to {actual}, expected {expected}"
)
Sources: scripts/validate_manifest.py:118-149
CI Workflow Hardening
The repository employs strict GitHub Actions configuration to prevent credential leakage and unauthorized execution.
- Pinned Actions: Every
uses:reference in the workflows must be pinned to a 40-character commit SHA, not a tag. - Shell Safety: Workflow expressions (
${{ ... }}) are kept inenvorwithfields and are never interpolated directly into executable shell text to prevent injection attacks. - Permission Scoping: Global
permissionsfor the build workflow are set tocontents: read. Only the specificreleasejob is grantedcontents: writeto publish the verified draft. - Runner Validation: The build matrix is constrained to specific, supported runners (e.g.,
macos-15-intel,ubuntu-latest) to ensure environment consistency.
Sources: scripts/test_workflow_safety.py:77-105, REVIEW.md:7-14
Summary
The numan-plugins security model is a defensive architecture that prioritizes artifact integrity and provenance over convenience. By treating releases as immutable transactions and strictly separating the build process from the cryptographic trust root, the system ensures that Nushell plugins are delivered securely to users without the risk of private key exposure or artifact tampering during the CI lifecycle.
Sources: README.md:86-90, REVIEW.md:42-50