pfsense-mcp-server
Safe, least-privilege pfSense access for AI assistants. MCP server that gives an AI assistant strongly typed, read-only visibility into one pfSense appliance — system, network, firewall, DHCP, DNS, VPN, certificates, and diagnostics — without raw shell access, an unaudited scripting surface, or any way to change the appliance by accident.
Key facts
- 84 public READ tools. 0 public WRITE tools by default.
- Covers roughly 80% of the useful READ capability surface identified by this project's own capability audit (267 OpenAPI paths / 243 GET operations reviewed, every one given an explicit disposition).
- Every one of the 84 tools has been exercised at least once against a real pfSense instance (LAB or production) before public registration and confirmed to return a response matching this project's typed model — never assumed from schema alone. Depth varies by tool: some were confirmed against real, populated data; others have so far only been observed returning a valid empty/default envelope on every system tested (noted per capability where relevant) — see Compatibility.
- Explicit capability → privilege mapping for every tool: least-privilege
pfSense identities are documented and derivable from source, not
guessed — see
docs/PFSENSE_LEAST_PRIVILEGE_MATRIX.md. - Strongly typed response models (Pydantic) for every tool — no untyped JSON passthrough.
- Secret-bearing fields are excluded from the model layer by construction wherever confirmed present (CA/certificate private keys, CARP shared secrets, WireGuard keys, OpenVPN client credentials) — never relied on upstream API redaction as the only safeguard.
What you can do
Ask your MCP client things like:
- "List my VLANs and show which physical interface each one rides on."
- "What static routes are configured, and where do they point?"
- "Is my WAN gateway up, and what's the current latency and packet loss?"
- "What firewall rules apply to the WAN interface?"
- "Show me every active DHCP lease and static mapping on the LAN."
- "What DNS resolver host overrides and access lists are configured?"
- "Are any IPsec tunnels established right now, and what are their child SAs?"
- "List my OpenVPN servers and their configured ciphers."
- "Which of my certificate authorities and certificates expire soon?"
- "What pfSense and REST API package version is this appliance running?"
Each maps to one typed, capability-gated tool actually present in the
public registry — see Capability overview for the
full breakdown and docs/API.md
for the complete reference. Nothing above requires WRITE access.
Why this server
I built this project because I wanted AI assistance for pfSense without giving an LLM the ability to accidentally disconnect my own network.
A firewall is not just another application. It is the foundation everything else depends on. Any software capable of changing firewall rules, routing, interfaces, DNS, VPN configuration, or other network-critical settings also has the ability to make that network unreachable — and "the model probably won't make a bad change" is not a safety mechanism, it's a hope. I believe those operations deserve a higher safety standard than simply exposing WRITE tools to an AI model.
This project deliberately started, and remains, READ-only by default. Not because WRITE is impossible — a fully authorized, recoverable WRITE path exists and is described in Security model — but because I believe WRITE should be earned through architecture rather than enabled by implementation.
What makes the engineering different, not just the policy:
- Explicit tool registration, never generic API dispatch. Every tool
is one statically checked, named function calling one client method —
there is no
call_endpoint(path, method)escape hatch an AI (or a bug) could use to reach an unregistered endpoint. - 84 READ / 0 default-WRITE public contract, enforced by an automated snapshot test — a change to the public surface that isn't reflected in the approved contract fails CI.
- Capability-based least privilege. Each tool is gated behind a named
Capability; profiles grant capability sets, not raw endpoint access. - Deterministic public-contract validation — the registered tool set, its privilege mapping, and its documentation are all re-derived from source and checked for drift on every run, not maintained by hand.
- Secret-bearing fields omitted from models where confirmed present, instead of trusting the upstream API's own redaction behavior.
- Verification before promotion — every tool is exercised against a real pfSense instance (LAB or production) and confirmed to return the expected shape before it is ever added to the public registry.
- Fail-closed handling of ambiguous or sensitive capabilities: unclear schema behavior is treated as unsafe until proven otherwise, not implemented optimistically.
- CI, CodeQL, and a dedicated release-validation pipeline gate every change — see Security model for what's actually enforced, not just designed.
The READ trust path, in one diagram:
flowchart LR
A["AI / MCP client"] -->|"stdio (trust boundary)"| B["Explicit registered<br/>MCP tool<br/>(1 of 84, no dispatcher)"]
B --> C["Capability / profile gate<br/>(auditor: READ only)"]
C --> D["Least-privilege mapping<br/>(exact pfSense privilege)"]
D --> E["One fixed typed<br/>client method"]
E --> F["pfREST GET<br/>(GET-only, enforced)"]
F --> G[("pfSense appliance")]
G --> H["Typed model boundary<br/>(secret fields excluded<br/>by construction)"]
H --> I["Safe MCP result"]
style A fill:#eee,stroke:#333
style G fill:#eee,stroke:#333
style C fill:#fff3cd,stroke:#856404
style D fill:#fff3cd,stroke:#856404
style H fill:#d1e7dd,stroke:#0f5132
Every one of the 84 tools takes this same path — no exceptions, no alternate route. The yellow boxes are hard gates (fail closed, not merely checked); the green box is where confirmed secret-bearing fields are structurally excluded, not filtered. See the full architecture diagrams page for the detailed sequence diagram this summarizes.
I don't mind if an AI answers a question incorrectly. I do mind if an AI accidentally disconnects my house from the Internet. That single design principle explains almost every architectural decision in this repository. See the security model for the complete picture, including the fully-built (but not default-reachable) WRITE path.
Quick start
python -m venv .venv
.venv/bin/python -m pip install --upgrade pip
.venv/bin/python -m pip install 'pfsense-mcp-server==0.5.1'
install -m 600 /dev/null /absolute/private/path/pfsense-api.key
# put the API key on the first line of that file, then:
{
"command": "/absolute/path/to/.venv/bin/pfsense-mcp-server",
"env": {
"PFSENSE_API_URL": "https://pfsense.example.invalid",
"PFSENSE_IDENTITY": "api-mcp-admin",
"PFSENSE_API_KEY_FILE": "/absolute/private/path/pfsense-api.key",
"PFSENSE_TLS_MODE": "strict"
}
}
Point your MCP client at that command — see MCP client setup
below for client-specific guides — confirm it shows 84 READ tools and no
WRITE tools, then try one of the example prompts
above. Full configuration reference, troubleshooting, and every
environment variable: docs/CONFIGURATION.md.
pfsense-mcp-server is published on
PyPI with
PEP 740 digital attestations verifiable
back to this repository and the exact release commit — no long-lived upload
token exists.
Requirements / Compatibility
- Python: 3.11, 3.12, or 3.13.
- pfSense REST API package (
pfrest/pfSense-pkg-RESTAPI): required, API v2. This project pins its models against the v2.10 schema.
pfSense edition/version compatibility
Evidence tiers used below — deliberately mutually exclusive, so no two rows can plausibly satisfy the same tier:
| Tier | Meaning |
|---|---|
| PRODUCTION VERIFIED | Exercised against a real production pfSense appliance, under an explicit, narrowly-scoped, owner-authorized READ-only verification pass. |
| LAB VERIFIED | Exercised against this project's controlled, disposable LAB appliance — never production. |
| SUPPORTED / COMPATIBLE | Not directly exercised on that exact release, but compatibility is established by this project's own stronger adjacent evidence (e.g. a schema fetch or tool invocation against a release one step away with proven zero drift) — more than a plausible expectation, short of a direct test. |
| EXPECTED COMPATIBLE / UNVERIFIED | A reasonable expectation from public vendor documentation, FreeBSD-generation similarity, or cross-release package-version behavior — but nothing this project directly exercised against that release. Do not read this as "supported." |
| Platform | Version | Status | Evidence |
|---|---|---|---|
| pfSense CE | 2.9.0 (FreeBSD 16.0-CURRENT, pfREST 2.10) | LAB VERIFIED | Current LAB baseline; full public contract exercised against a disposable, isolated appliance. |
| pfSense CE | 2.8.1 (pfREST 2.10) | LAB VERIFIED | Prior LAB baseline; this project's READ-expansion audit's initial 7-tool backlog was verified here before the LAB's platform upgrade to CE 2.9.0. |
| pfSense Plus | 26.07-RELEASE | PRODUCTION VERIFIED | Owner-authorized, READ-only production compatibility pass: 82 of 84 public tools invoked successfully with real data (30 valid-empty results); the remaining 2 (WireGuard status) correctly and automatically classified as package-absent, not a compatibility failure. The REST API package's own self-reported version (pfsense_get_system_restapi_version's current_version field) was directly confirmed as v2.10 — identical to both CE LAB baselines below. Schema-level: the live OpenAPI schema matched the pinned v2.10 reference exactly — 267/267 paths, 186/186 components; the only differences found across every field in every component were 5 instance-specific runtime default values, never a type or nullability change. Zero secret-bearing fields present in any exercised response. |
| pfSense Plus | 25.11 | EXPECTED COMPATIBLE / UNVERIFIED | No live or LAB access to a 25.11 instance was available — nothing in this project has directly exercised a schema fetch, tool call, or package inspection against this specific release. The evidence available is entirely adjacent: the same pfREST v2.10 package this project directly confirmed (via pfsense_get_system_restapi_version, not inferred) on CE 2.8.1, CE 2.9.0, and Plus 26.07 already spans three different platform release numbers across both editions without incident; and Netgate's own published 25.11 release notes state its base OS was updated to FreeBSD 16-CURRENT, matching the CE 2.9.0 LAB baseline's directly-observed FreeBSD generation. That is a reasonable expectation, not this project's own stronger evidence — hence EXPECTED COMPATIBLE / UNVERIFIED, not SUPPORTED / COMPATIBLE. |
pfSense platform version, edition, FreeBSD generation, and REST API package variant/version are five independent facts, not proxies for one another:
- Platform version (e.g.
26.07-RELEASE) and edition (CE vs. Plus) are read directly frompfsense_get_system_version/pfsense_get_system_status. - REST API package version: self-reported (
pfsense_get_system_restapi_version'scurrent_versionfield) and directly, independently confirmed as v2.10 on three separate live systems — the CE 2.8.1 LAB, the CE 2.9.0 LAB, and, via this release's owner-authorized production pass, pfSense Plus 26.07. That identical version string held across three different platform release numbers and both editions, so REST API package versioning is not required to numerically match the pfSense platform version. - REST API package variant: on every platform this project has
directly tested — the CE 2.9.0 LAB (which lists only the one other
package actually installed there) and the Plus 26.07 production
appliance (which lists 9 other installed packages) — the REST API
package does not appear as a discrete named entry in
pfsense_get_system_packages, the REST API's own general installed-package listing endpoint. This omission is a property of that one pfREST endpoint, confirmed identical on both CE and Plus — it says nothing about, and must not be confused with, the appliance's underlying FreeBSD/pfSense package database. The REST API package itself is unambiguously real and versioned: the dedicated version-check endpoint above directly confirmsv2.10on every platform tested. An earlier draft of this document incorrectly concluded from this same endpoint's output that the REST API "ships as a built-in platform component" on Plus specifically — that was wrong on both counts (Plus does run a real, versioned REST API package, and the omission is not a CE-vs-Plus difference); seeCHANGELOG.mdfor the correction record. - Schema/API compatibility: verified independently of the above, by direct structural comparison (267/267 paths, 186/186 components) — see the Plus 26.07 row above.
Package-conditional tools. Every one of the 84 registered tools'
underlying endpoints was checked against the pfREST schema's own
declared package requirements, not assumed. Two tools
(pfsense_get_status_wireguard_tunnels, pfsense_get_status_wireguard_peers)
require pfSense-pkg-WireGuard in practice: they return an
automatically-classified package-absent result (HTTP 404,
MODEL_MISSING_REQUIRED_PACKAGE) — never an error — when the package
isn't installed, confirmed directly on both the LAB and production
systems used for this project's testing.
Four further tools' endpoints declare a required package in the
schema's own metadata but do not gate on it in practice, confirmed
by direct invocation against systems that genuinely lack the declared
package: pfsense_get_acme_settings (schema declares
pfSense-pkg-acme), pfsense_get_bind_settings (pfSense-pkg-bind),
pfsense_get_cron_jobs (pfSense-pkg-Cron), and
pfsense_get_freeradius_eap (pfSense-pkg-freeradius3) all returned a
successful, real response on the CE 2.9.0 LAB (which has none of these
four packages installed) and on the Plus 26.07 production appliance
(which has none of the first three, though pfSense-pkg-Cron happens
to be installed there). These read as stored configuration/default
settings structures rather than genuinely package-gated runtime state,
unlike the WireGuard status pair, which report live package-dependent
state and do 404 when absent. Do not assume a schema-declared package
requirement reflects actual runtime gating without direct verification
— this project checked, rather than guessed.
MCP client setup
Guides exist for Claude Desktop, Claude Code, Codex CLI, ChatGPT desktop
(via the Codex host), Cursor, VS Code, and Continue — see
examples/README.md
for the complete, copy-pasteable set. Every guide configures the same
local-stdio launch shown in Quick start above; only the
client-specific config file location and key differ.
Capability overview
| Category | Tools | Examples |
|---|---|---|
| System | 24 | hostname, timezone, DNS, console, version, packages, REST API settings, cron, ACME, diagnostics |
| Firewall | 14 | rules, aliases, states, NAT (outbound / 1:1 / port forward), schedules, virtual IPs, traffic shapers |
| VPN | 14 | IPsec (Phase 2, encryption options, live SA/child-SA status), OpenVPN (server config, client-specific overrides, live status), WireGuard status, CARP |
| DHCP | 6 | servers, static mappings, leases, relay, address pools, custom options |
| Interfaces | 8 | interface status, VLANs, groups, bridges, GRE, LAGG, available interfaces |
| DNS | 5 | resolver settings, host/domain overrides, access lists, forwarder overrides |
| Routing / Gateways | 5 | gateways, gateway status, gateway groups, default gateway, static routes |
| Certificates / PKI | 3 | certificates, certificate authorities, CRLs |
| Users / API identities | 3 | local users, user groups, API keys |
| Services / Monitoring | 2 | service status, FreeRADIUS EAP |
84 tools total. Full per-tool reference, parameters, and security notes:
docs/API.md.
Security model
- Credential fields (API keys, passwords, private keys) never appear in a public model, MCP schema, log line, or exception message — by construction, not filtering.
- Fail-closed configuration and strict TLS by default.
- Explicit capability gates: an MCP tool is reachable only if its capability is in the selected profile's accepted set.
- The supported transport is local stdio; the process controlling that channel is the trust boundary — see the threat model for exactly what that does and does not cover.
Every claim above is backed by a specific test class, listed with the tests
that enforce it in SECURITY.md. Report
vulnerabilities privately through SECURITY.md — never in a
public issue.
Protected WRITE architecture (built, not default-reachable)
pfSense MCP Server ships a fully built, twice live-verified WRITE architecture that remains unreachable unless an operator explicitly opts in. State-changing operations are protected by a transaction-oriented security architecture designed for AI-initiated infrastructure changes — not simply an API credential placed behind an MCP tool.
- Zero WRITE capabilities exposed by default — reaching the one accepted
WRITE tool requires an operator to explicitly select
PFSENSE_PROFILE=write_protected. - Least-privilege pfSense credentials, scoped to exactly the REST endpoints the WRITE path needs — never a broad/administrative account.
- Deterministic plan and execution-intent binding — the exact target state is bound cryptographically before anything is authorized.
- Ed25519-signed authorization, produced off-host by a human operator, short-lived and single-use.
- Fresh-state revalidation before execution, with stale-state and concurrent-change refusal.
- A
RecoveryContractstate machine governing every mutation's full lifecycle, durable and auditable, with independently signed confirmation from a separate confirmation authority. - Deterministic execution with authoritative read-back verification and fail-closed reconciliation on any ambiguous outcome — never a blind retry.
- Integrity-protected, MAC-authenticated audit trail and state.
- Hardware-backed TPM monotonic witness for anti-rollback protection — the only anchor-assurance backend implemented today (a software-only posture is modeled but has no backend yet) and required for a plan to be considered safe to proceed in production.
The authorization path, in one diagram:
flowchart LR
A["Default profile:<br/>0 WRITE tools<br/>(not reachable)"] -.->|"explicit operator<br/>opt-in required"| B["write_protected profile<br/>+ full Tier 1 material<br/>provisioned"]
B --> C["Off-host signed<br/>authorization + confirmation<br/>(separate identities)"]
C --> D["6 fail-closed gates<br/>(signature, expiry, digest,<br/>freshness, one-time use)"]
D --> E["Sealed MutationExecutor<br/>(only path that ever sends)"]
E --> F["Authoritative read-back"]
F --> G{"Outcome?"}
G -->|"confirmed"| H["VERIFIED"]
G -->|"ambiguous"| I["RECONCILIATION<br/>(never blind retry)"]
style A fill:#f8d7da,stroke:#842029
style H fill:#d1e7dd,stroke:#0f5132
style I fill:#fff3cd,stroke:#856404
style E fill:#cfe2ff,stroke:#084298
Implemented, verified, and default-reachable are three different things — do not conflate them. This path is implemented (real code, shown above) and verified (twice, end-to-end, against a real disposable LAB appliance) for exactly one operation. It is not default-reachable: an operator must explicitly opt out of the default profile, and even then an AI cannot single-handedly produce a valid authorization — that requires an off-host Ed25519 signature the running server never holds the key for. See the full authorization-path diagram for the gate-by-gate detail and the defense-in-depth diagram for how this fits alongside the READ path.
verified=True does not mean WRITE is enabled by default.
Verification, profile/posture selection, authorization, confirmation,
freshness, target state, and execution are separate, independently
enforced gates — every one of them must hold for a given mutation, every
time. The protected WRITE path has been exercised end-to-end twice,
each time independently verified, against a real disposable LAB pfSense
appliance — never production/home pfSense — including least-privilege
execution through a dedicated 4-privilege pfSense identity, authoritative
read-back, full RecoveryContract lifecycle, and TPM hardware witness
advancement confirmed against the physical device both times. See
docs/adr/ADR-026-first-write-capability-adapter.md
for the complete evidence chain, and
the Tier 1 architecture for the full design.
Troubleshooting
| Symptom | Likely cause | Diagnostic action |
|---|---|---|
| MCP client shows 0 tools / fails to start | Wrong command path, or the venv's pfsense-mcp-server entry point isn't executable |
Run the command directly from a shell with the same env block; read the stderr output verbatim |
configuration error on startup |
A required environment variable is missing or malformed | Compare against docs/CONFIGURATION.md — the process fails closed rather than guessing a default |
401/authentication failures |
Wrong API key, wrong PFSENSE_IDENTITY, or the key file has the wrong permissions/first-line format |
Re-verify the key file's first line matches the key shown in pfSense's REST API user settings; confirm the identity string matches the pfSense user the key belongs to |
403/insufficient-privilege failures on specific tools |
The pfSense identity lacks the narrow privilege that tool needs | Cross-check the required privilege in docs/PFSENSE_LEAST_PRIVILEGE_MATRIX.md against the identity's assigned privileges in pfSense |
| TLS/certificate verification errors | PFSENSE_TLS_MODE=strict against a self-signed or internal-CA certificate |
Either install a trusted certificate on pfSense, or set PFSENSE_TLS_MODE=auto with PFSENSE_TLS_CA_FILE pointed at your internal CA — never disable verification entirely |
| A specific tool always returns an empty/package-absent result | The underlying pfSense feature or package (e.g. WireGuard) isn't installed/configured on that appliance | Confirm via pfSense's own package manager or configuration UI; this is expected behavior, not a bug — see Compatibility |
| REST API unreachable / connection refused | The pfrest/pfSense-pkg-RESTAPI package is disabled or not installed on the appliance |
Confirm the package is installed and enabled in pfSense's package manager; confirm PFSENSE_API_URL and network reachability |
| Works on one pfSense version, not another | A genuine platform/schema incompatibility | Check Compatibility for your exact platform/version combination before filing an issue |
| Timeouts under load | Default HTTP timeouts, or the pfSense appliance is under heavy load | Confirm the appliance itself is responsive via its own web UI; this project does not currently expose a configurable timeout — see docs/CONFIGURATION.md for current limits |
None of the above are resolved by weakening TLS verification, broadening
credentials, or selecting write_protected — if a READ tool fails, the
fix is almost always a narrower privilege grant or a configuration
correction, not a broader one.
Documentation
A browsable version of the full documentation set below is published at
night4me.github.io/pfsense-mcp-server
(built with make docs-serve for a local preview); see
docs/index.md for the same map.
- MCP tool reference
- Configuration reference
- Client setup examples
- Least-privilege matrix
- Security model · Threat model
- Architecture diagrams · Architecture decisions
- Tier 1 safety architecture · Public roadmap
- Contributing · Support · Security policy
Release status
v0.5.1 is the immutable production baseline, published on PyPI —
84 READ tools, 0 WRITE tools. A documentation-accuracy and
security-communication patch over v0.5.0 — no MCP capability change,
no runtime security-semantic change, public contract byte-identical to
v0.5.0. It corrects an error v0.5.0 shipped with (an incorrect
pfSense Plus REST API packaging claim, found via post-publication
review), completes the package-dependency documentation, sharpens the
compatibility evidence-tier terminology, downgrades an overstated
pfSense Plus 25.11 classification, and adds three new architecture
diagrams explaining the READ and protected-WRITE security paths. See
CHANGELOG.md's [0.5.1] entry for the complete list.
v0.5.0 itself was a major READ-capability expansion over the prior
v0.4.2 baseline — exactly a 100% increase in public READ tool count
(42 → 84) — driven by a comprehensive capability discovery audit that
found the prior 42-tool contract covered only ~40% of the useful READ
capability universe; this release covers roughly 80%. v0.5.0's own
tag, GitHub Release, and PyPI artifacts remain unmoved as an accurate
historical record, including the packaging-claim error later found and
corrected here — see CHANGELOG.md's [0.5.0] entry for that
release's complete, tool-by-tool list and every security-relevant
finding along the way.
The one WRITE capability this repository has ever added
(set_firewall_alias_description_v1) is verified=True following
independently-verified live evidence (ADR-026), but remains
unreachable under the default profile, requires an operator to
explicitly opt into write_protected, and still requires a real,
owner-driven signing ceremony for every individual mutation — see
the security model's "Recovery and WRITE status"
section for the precise, current description. The Tier 1 safety
framework described above remains implemented, tested, structurally
isolated code, unchanged by this release.
v0.4.2 was a documentation/packaging-presentation patch over v0.4.1
— no functional or security-relevant change — and remains a valid,
installable historical release, as does v0.4.1 itself. v0.4.0 was
tagged and its GitHub Release published, but its PyPI publication
failed outright before any upload was attempted — see CHANGELOG.md's
[0.4.1] entry for the full root cause; per this project's release
policy, v0.4.0's tag/Release are preserved unmoved as an accurate
historical record. See
docs/ROADMAP.md for what's next.
Contributing
Contributions are welcome within the documented security and approval boundaries. Read CONTRIBUTING.md before opening a change.
License
Licensed under the MIT License.
Metadata
Release files for pfsense-mcp-server 0.5.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pfsense_mcp_server-0.5.1.tar.gz | 401.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pfsense_mcp_server-0.5.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 856.4 kB
Release files / pfsense_mcp_server-0.5.1.tar.gz
| Download URL | pfsense_mcp_server-0.5.1.tar.gz |
|---|---|
| Size | 401.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
21db65fcc20f8654681a6e777b4f3d081de72775ff7713f40031ed7f6947717a
|
|
BLAKE2b-256 checksum How to use checksums |
265275020a9e4e0a5fe71b84968719a44d64f3d37e8678507485462e5eb6e7d4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 21, 2026.
Transparency logRelease files / pfsense_mcp_server-0.5.1-py3-none-any.whl
| Download URL | pfsense_mcp_server-0.5.1-py3-none-any.whl |
|---|---|
| Size | 454.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fb76dac5ed7ed33e5b696f0ab3a62f5ded2cacd2b659f5a309c8b9dbf07fbea1
|
|
BLAKE2b-256 checksum How to use checksums |
90c5e18395216890b815e0a1888a4a03a44505c370d68d9151c68d2d8311b0e8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 21, 2026.
Transparency log