This release is a pre-release and may not be stable for production use.
redact-secret-vault (Python)
Status: alpha / research-grade. Native Python implementation of the same
S1 server-authority contract as the JavaScript
@redact-secret/vault-server
(decision record).
It does not implement the JavaScript @redact-secret/vault API: Python has
no authority-free portable vault, so this single distribution is named
redact-secret-vault without a -server suffix (see the
naming decision's 2026-09-28 note).
The distribution was called redact-secret-vault-server (module
redact_secret_vault_server) before its first publish; that name was never on
PyPI. It provides trusted principal/tenant resolution, a source→sink/path/purpose decision
tuple, fail-closed policy evaluation, an extended denial vocabulary, and
audit events with no field capable of carrying a restored value. Storage is
in-memory only, matching @redact-secret/vault's threat boundary — nothing
here is persistent. Version 0.1.0a3 (PEP 440; the counterpart of the npm
0.1.0-alpha.3 release) is the first version to be published to PyPI, from
release.yml through trusted publishing (see
RELEASING.md). Until that publish has run, it is
installable only from this repository.
This package does not implement secret detection. @redact-secret/core has
no published Python distribution (verified against the
redact-secret/redact-secret GitHub organization on 2026-09-27: only
packages/javascript exists there). Capture therefore uses a qualified
service boundary: NodeCoreBridge
shells out to a small Node.js script
(boundary/core_bridge.mjs)
that calls only the core's public scan API and returns its safe finding
metadata (never a matched value). See
docs/research/python-server-integration-2026-09-27.md
for the full inventory, the gap this leaves, and what "equivalent to the JS
implementation" means here.
Requirements
- Python 3.10+
- For
NodeCoreBridge: anodeexecutable (Node.js 20, 22, or 24) onPATH, and@redact-secret/coreat exactly the pinned version (PINNED_CORE_VERSION,0.1.0-beta.10) installed with npm in a directory your application owns. A consumer that supplies its ownCoreClientdoes not need Node at all — the boundary is aProtocol, not a hard dependency.
Install
pip install redact-secret-vault==0.1.0a3
# In a directory of your choice, for example /srv/myapp/core:
npm install @redact-secret/core@0.1.0-beta.10
Then tell the bridge where that node_modules is, either in code or through
the environment:
bridge = NodeCoreBridge(node_modules="/srv/myapp/core/node_modules")
export REDACT_SECRET_VAULT_NODE_MODULES=/srv/myapp/core/node_modules
The explicit node_modules= argument wins over the environment variable. The
bridge then loads <node_modules>/@redact-secret/core from exactly that
directory. It never searches parent directories or the working directory, so
whoever controls the process's working directory cannot substitute the core.
A relative path is made absolute when the bridge is constructed. The reported
core version must still equal PINNED_CORE_VERSION
(CORE_VERSION_MISMATCH otherwise). A directory without the core raises
CORE_FAILURE with core_code="BRIDGE_CORE_NOT_FOUND", or
BRIDGE_CORE_LOAD_FAILED if the core is there but fails to load. Neither
error includes the path.
With neither setting, the bridge script resolves the core relative to its own
location in site-packages. That works when the virtualenv lives inside the
project that ran npm install (for example /srv/myapp/.venv with
/srv/myapp/node_modules), and in this repository. For a virtualenv anywhere
else it fails with BRIDGE_CORE_NOT_FOUND, so pass node_modules=.
From this repository (development; npm ci at the root installs the core):
cd packages/vault-py
pip install -e ".[test]"
Usage sketch
import asyncio
from redact_secret_vault import (
CaptureGrant,
CaptureOptions,
InMemoryVaultServer,
NodeCoreBridge,
PolicyDecision,
Principal,
RestoreRequest,
)
def resolve_principal(context):
# The consuming application's own authentication — never a mandated
# identity provider. Must raise, not return a partial Principal, when
# trust cannot be established.
return Principal(id=context["user_id"], tenant=context["tenant"])
def same_tenant_only(decision_input):
if decision_input.tenant == decision_input.source.issued_tenant:
return PolicyDecision(allow=True)
from redact_secret_vault import ServerDenialReason
return PolicyDecision(allow=False, reason=ServerDenialReason.TENANT_MISMATCH)
async def main() -> None:
server = InMemoryVaultServer(
core_client=NodeCoreBridge(),
principal_resolver=resolve_principal,
release_policy=same_tenant_only,
)
captured = server.capture(
"deploy with ghp_EXAMPLE_SYNTHETIC_TOKEN_0000000000 now",
CaptureOptions(
issued_tenant="tenant-acme-synthetic",
release=(CaptureGrant(sink="reply", paths=("body",)),),
),
)
result = await server.restore(
RestoreRequest(
sink="reply",
captures=(captured.capture_id,),
fields={"body": f"Use {captured.tokens[0].token} please"},
purpose="support-reply-purpose-synthetic",
tenant="tenant-acme-synthetic",
context={"user_id": "user-synthetic-1", "tenant": "tenant-acme-synthetic"},
)
)
print(result.fields["body"])
asyncio.run(main())
PII selection and retention
Status: implemented since 0.1.0a2 (never published); 0.1.0a3 is the first PyPI release. PII detection needs
@redact-secret/core@0.1.0-beta.10, which this repository pins
(PINNED_CORE_VERSION). A core without PII support (0.1.0-beta.9) gets the
fail-closed rules below. The rules are
the PII retention and activation decision record
(§1 and §3 "Python bridge"), the same ones @redact-secret/vault follows.
- Selection.
NodeCoreBridge(pii=[...])forwards the selectors verbatim to the core'sinitialize({ pii })in each bridge process. Each scan runs in a fresh Node.js process, so this list is the only selection. Omitting it (the default()) means PII off. The core judges selector grammar; its rejections surface asCORE_FAILUREwithcore_code(for examplePII_SELECTOR_INVALID). - Identity. Each scan reports the core's
piiActivation()identity asCoreScanOutcome.pii_activation, orNonewhen the core has no PII support. The bridge pins the identity from its first successful scan and raisesPII_ACTIVATION_MISMATCHif a later scan differs. Passexpected_pii_activation=to compare against a fixed identity instead. - Retention. A
redactfinding whose type starts withpii_is never retained unless its exact type is listed inCaptureOptions(pii=PiiRetention(retain=("pii_global_iban", ...))). Aneligiblecallback is not called for unlisted PII types and can only narrow the list. Unretained PII is replaced by a non-restorable placeholder and counted inunrestorable. - Fail closed. A non-empty
piiselection or anexpected_pii_activationon a core without PII support, and a capture'spiiretention when the scan reported no active PII detection, each raisePII_UNAVAILABLE.
bridge = NodeCoreBridge(pii=["pii"]) # the pinned beta.10
options = CaptureOptions(
issued_tenant="tenant-acme-synthetic",
release=(CaptureGrant(sink="reply", paths=("body",)),),
pii=PiiRetention(retain=("pii_global_iban",)),
)
With PII on, Medium- and Low-confidence PII findings default to warn, so a
capture containing them fails with UNREDACTED_FINDINGS unless the caller
passes a policy that maps them or unredacted="pass-through". For example,
beta.10 rates a labeled seven-digit local phone number (telephone=…) as
Medium pii_global_phone. pii.retain applies only to redact findings, so
listing a warn-level type there does not retain it.
Every finding the core returns, PII included, counts toward max_findings,
which the bridge passes to the core. Exceeding it raises CORE_FAILURE with
core_code="FINDING_LIMIT_EXCEEDED" and commits nothing.
This package has no displayFormatter: it builds its own output with
<SECRET_n> placeholders and never calls the core's redact(). The core
beta.10 rule that rejects a placeholder reproducing any finding's matched text
(INVALID_PLACEHOLDER, which the JavaScript vault surfaces from a custom
displayFormatter) therefore does not apply here.
Tests
pip install -e ".[test]"
pytest
tests/test_pii_bridge.py includes four cases that need a PII-capable core;
they run against the pinned beta.10. VAULT_SERVER_PY_PII_CORE_NODE_MODULES
points them at another node_modules (for example a local core build). Four
further cases need a core without PII support (beta.9) and skip with a reason;
the fake-core cases in the same file cover those rules.
tests/test_conformance.py runs the shared language-neutral corpus
(conformance/v1/corpus.json) against this package; tests/test_server_authority.py
covers the S1-specific adversarial cases (cross-tenant, missing purpose,
revoked-token reuse, policy-evaluation-error, principal-resolution failure)
that the corpus does not yet include (see conformance/README.md). Both
require a node executable and @redact-secret/core installed at the
repository root (npm ci from the repo root first).
What "equivalent to @redact-secret/vault-server (JS)" means
See docs/research/python-server-integration-2026-09-27.md for the full statement and the candid differences (token entropy source, marker-detection regex, capture's audit vocabulary, and the core-integration boundary itself). In short: the same decision tuple, the same nine-step preflight order, the same denial vocabulary, and audit events that structurally cannot carry a restored value — not byte-identical code or wire format.
Metadata
Release files for redact-secret-vault 0.1.0a3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| redact_secret_vault-0.1.0a3.tar.gz | 47.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| redact_secret_vault-0.1.0a3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 81.4 kB
Release files / redact_secret_vault-0.1.0a3.tar.gz
| Download URL | redact_secret_vault-0.1.0a3.tar.gz |
|---|---|
| Size | 47.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f21365c17239822321eef54e0abb41debcc2d36f48f1b099110e98207a48ec5d
|
|
BLAKE2b-256 checksum How to use checksums |
a65777e58547e5ae81341b0d261f8baba494560837c56386b34dbccd1c6043b9
|
| 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 Sep 28, 2026.
Transparency logRelease files / redact_secret_vault-0.1.0a3-py3-none-any.whl
| Download URL | redact_secret_vault-0.1.0a3-py3-none-any.whl |
|---|---|
| Size | 33.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
11634bf8a66ecc37f5dfcc6a2bb8d0519237fe567e1eaff375f804dd7fe5c4ca
|
|
BLAKE2b-256 checksum How to use checksums |
0bb5114a249f14019fbc26d345d0c0c6de753c0aecb318da303b8b856eb88b0d
|
| 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 Sep 28, 2026.
Transparency log