🌀 ObfusAI
A prompt-hygiene toolkit for LLM apps: catch secrets in prompts, detect when a model leaks its instructions, optionally obfuscate, and measure whether any of it works on your model.
Python library, plus a local gateway for every other language. Zero dependencies · Python 3.9+ · OpenAI · Anthropic · LangChain · LlamaIndex
English · 简体中文
⭐ If ObfusAI is useful to you, give it a star on GitHub — it takes one click and helps other developers find it.
What problem does this solve?
If your system prompt or tool schemas carry business logic (thresholds, pricing rules, scoring, internal codenames), five different things can go wrong. They need different tools, and they are not equally well solved:
| # | Problem | What ObfusAI gives you | How far to trust it |
|---|---|---|---|
| 1 | Secrets and personal data end up in prompts (API keys, card numbers, customer e-mails) | audit / obfusai audit: checksum-validated detectors for cards, IDs, IBANs, keys, JWTs, e-mails, phones ... |
Good for known formats. Heuristic: it lowers the odds, it does not prove a prompt is clean |
| 2 | A model discloses its instructions ("repeat everything above") and nobody notices | LeakDetector: scan and redact replies for protected values, aliases and anchors. Works on plaintext prompts too (values=[...]) |
Good at detecting values you listed. It detects, it does not prevent |
| 3 | You don't know how leaky your prompt really is, or whether a change broke it | evaluate_extraction (9 extraction attacks, leak rate) and evaluate_fidelity (does behaviour survive?), one-command script for a real model |
Measures your model, so trustworthy for that model and prompt |
| 4 | Plaintext logic is spread across logs, traces, dashboards, screenshots | Obfuscation: numbers, keywords and identifiers become symbols, with a decoding table appended | Weak and not free. Protects against glancing, not reading (details below) |
| 5 | Your stack is not Python | obfusai serve: a local gateway; point any language's OpenAI/Anthropic SDK at it |
Works. Verified on the JS SDKs and HTTP clients in Go, Java, Ruby, PHP |
| 6 | Skill prompts are stored or shipped as plaintext files (repo, package, backup, marketplace) | seal: AES-256-GCM encryption at rest; the gateway opens sealed prompts server-side, so clients only hold ciphertext |
Real encryption, but only against people without the key. Whoever runs the prompt, and the model provider, still see plaintext |
| 7 | A prompt leaked and you need to know which copy it came from | canary: a unique token per recipient, found again with one call |
Evidence, not prevention; an attacker who reads the prompt can strip a token they notice |
So the honest summary is: the reliable parts are the audit, the leak detector and the measurement tools; obfuscation is an optional extra whose value you must measure.
What obfuscation does and does not do
BEFORE AFTER (medium)
You are the Alpha Momentum Engine for You are the λ for τ.
Helios Capital. Signal "π": Ψ when δ > ξ and Θ <= θ.
Signal "mean_reversion_v7": BUY when Never exceed φ of Ξ. Hard stop: β = μ.
momentum_score > 0.73 and risk_budget_bps <= 45.
Never exceed 2.5% of NAV. <obfusai-context v="1" id="2d5d5ff8">
Hard stop: drawdown_limit = 8.2%. ...decoding instructions...
λ = "Alpha Momentum Engine"
δ = "momentum_score" ξ = "0.73" ...
</obfusai-context>
- The symbols replace the values in the text, but the decoding table travels in the same message. At
low/mediumthe table holds the real values in plain text, so a search formomentum_scorestill finds it (in the table). Athighthe definitions are reversed and decoys are mixed in, which defeatsgrepand a casual read, but not a person or a model. - A model that repeats its context repeats the table with it. In this repo's unit test (
tests/test_redteam.py) an echoing model leaks 100% of the protected values with and without ObfusAI. - It is not free: a short prompt grows ~3-4x because of the table, and a model may follow the decoded instructions less well than the plaintext ones.
- Use it when the prompt must carry the logic and the risk you care about is casual exposure (someone skimming a trace, a cropped screenshot). Do not use it as a confidentiality measure.
What it does not solve
- Confidentiality against a determined attacker. This is obfuscation, not encryption, and not a security boundary.
- Credentials. Never obfuscate them: remove them (
auditfinds them). - If you need real confidentiality, change the architecture: keep the logic server-side and let the model call a tool that returns a result, not a rule; never put secrets in prompts; rate-limit and filter outputs. ObfusAI complements that, it does not replace it.
Before relying on any of this, measure it:
python examples/validate_real_model.py --yes(needs your API key; see Measure before you trust it). Nothing in this repo has been measured against a live model yet.
Skill prompts at rest, leak tracing, and distillation
Keeping skill prompts out of plaintext storage
A model has to receive the plaintext, so a prompt cannot be hidden from the machine that executes it or from the model provider. What you can do is keep it encrypted everywhere else (git, packages, backups, build caches, marketplace listings) and decrypt it only where you control the infrastructure:
export OBFUSAI_SEAL_KEY="$(obfusai keygen)" # keep it in a secret manager, never in the repo
obfusai seal skills/desk.md --label desk -o skills/desk.sealed # one URL-safe line, safe to commit and ship
obfusai serve --port 8787 # the gateway holds the key
# clients send the sealed line instead of the prompt; the gateway decrypts, obfuscates, forwards
from obfusai.seal import seal, unseal # pip install "obfusai[seal]"
blob = seal(prompt, key=key, label="desk") # AES-256-GCM; the label is authenticated (blobs cannot be swapped)
prompt = unseal(blob, key=key, expect_label="desk") # in-process, just before the call
If you distribute a skill that runs on the customer's machine with their own API key, they can always read it (they can inspect the request), and no encryption changes that. The architecture that works is to keep execution, and the key, on your side: customers call your service, and only ciphertext (or just a skill name) ever leaves it. Without the key, the gateway rejects requests that carry a sealed blob; it never forwards one.
Tracing which copy leaked
from obfusai.canary import embed_canaries, find_recipients
copy = embed_canaries(prompt, "acme-corp", secret, count=3) # a unique token per recipient, spread through the text
find_recipients(leaked_text, ["acme-corp", "globex"], secret) # -> ["acme-corp"], no database needed
Use it on a leaked prompt, or on a suspect product's reply to an extraction attempt. It is evidence, not prevention: a reader can strip tokens they notice, and comparing two copies shows where they differ.
Distillation: what ObfusAI does not do
Distillation trains a model on the outputs of your system (input/output pairs, sometimes reasoning traces). Hiding or obfuscating the prompt does not change what the outputs reveal, so a competitor can approximate your behaviour without ever seeing the prompt. ObfusAI does not prevent that, and nothing at the prompt layer can. What helps is operational:
- authentication and per-key quotas, and detection of scripted, high-volume, low-diversity query patterns;
- exposing less: no raw reasoning traces or logprobs, bounded output length and format;
- contractual terms you are prepared to enforce, and evidence that a copy came from you (the canaries above cover the prompt side only).
If the value of your skill is its behaviour rather than its wording, hiding the prompt is the wrong lever.
Features
| 🔐 Sealed prompts | obfusai.seal: AES-256-GCM encryption at rest with authenticated labels; obfusai serve opens sealed placeholders server-side and fails closed without the key (needs pip install obfusai[seal]) |
| 🕵 Leak tracing | obfusai.canary: per-recipient tokens (embed_canaries, find_recipients) to learn which copy of a prompt leaked |
| 🔎 Prompt audit | audit() / obfusai audit finds secrets, cards, IDs, IBANs, phones, e-mails, IPs in a prompt, with checksum validation and masked previews |
| 🛡 Leak guard | LeakDetector scans and redacts model output for protected values, aliases, anchors; works with or without obfuscation |
| 🧪 Red-team + fidelity harness | evaluate_extraction and evaluate_fidelity measure leak rate and behaviour on your model, plaintext vs obfuscated |
| 🌍 Any language | obfusai serve gateway: point the OpenAI/Anthropic SDK of any language (JS/TS, Go, Java, C#, Rust, Ruby, PHP ...) at it; JSON and SSE replies decoded |
| 🔌 Python integrations | OpenAI (Chat Completions and Responses API) and Anthropic wrappers (sync, async, streaming), LangChain and LlamaIndex adapters |
| 🔣 Obfuscation (optional) | Numbers, identifiers, quoted strings, acronyms, e-mails, URLs, IPs, dates, your keywords/regexes -> Greek aliases; three intensities; per-field policies; stable aliases for prompt caching; tool-call arguments and streams decoded |
| ⚓ Context anchoring | Self-describing <obfusai-context> block (English or Chinese, full or compact) tells the model how to decode |
| 🪶 Zero-dependency core | Pure standard library, strictly typed, never crashes on empty or odd input |
Install
pip install obfusai # core, no dependencies
pip install "obfusai[langchain]" # + LangChain adapter
pip install "obfusai[llamaindex]" # + LlamaIndex adapter
# latest development version:
pip install git+https://github.com/GANGMU-SBOM/ObfusAI.git
Quickstart
Start with the parts that are reliable, in this order.
1. Audit your prompts for secrets and personal data (CI-friendly: exit code 1 when something is found)
obfusai audit prompt.txt
from obfusai import audit
report = audit(system_prompt)
if not report.clean:
print(report.counts) # {"secrets": 1, "emails": 2}, previews are masked
2. Guard replies against disclosure of values you never want users to see
from obfusai import LeakDetector
guard = LeakDetector(values=["mean_reversion_v7", "0.73", "Helios Capital"]) # no obfuscation required
if guard.scan(model_reply).leaked:
model_reply = guard.redact(model_reply)
3. Measure how leaky your prompt is on your model (needs your API key)
python examples/validate_real_model.py --yes
4. Obfuscate (optional), only if the measurement says it helps:
from obfusai import ObfusAIComponent
engine = ObfusAIComponent("medium", seed="my-secret", keywords=["Helios Capital"])
result = engine.obfuscate(system_prompt)
result.prompt # obfuscated text + anchor → send as the system message
result.restore("η is the driver") # decode aliases in a reply
result.stats.growth # output size ÷ source size
Protect a whole chat request
protected = engine.obfuscate_payload({
"messages": [{"role": "system", "content": SYSTEM}, {"role": "user", "content": q}],
"tools": TOOLS,
})
client.chat.completions.create(model="gpt-4o", **protected.payload)
Or wrap your SDK client
from openai import OpenAI
from obfusai.adapters.openai import ObfusAIOpenAI
client = ObfusAIOpenAI(OpenAI(), ObfusAIComponent("high", seed="my-secret"))
reply = client.chat.completions.create(model="gpt-4o", messages=[...], tools=[...]) # streaming works too
reply = client.responses.create(model="gpt-4o", instructions=SYSTEM, input="hi", tools=[...]) # Responses API
print(reply.output_text) # decoded
parsed = client.responses.parse(model="gpt-4o", instructions=SYSTEM, input="hi", text_format=Signal) # structured output
with client.responses.stream(model="gpt-4o", instructions=SYSTEM, input="hi") as stream: # stream helper
for event in stream: # delta / snapshot / done events are decoded
...
final = stream.get_final_response() # decoded
from anthropic import Anthropic
from obfusai.adapters.anthropic import ObfusAIAnthropic
client = ObfusAIAnthropic(Anthropic(), ObfusAIComponent("medium", seed="my-secret"))
reply = client.messages.create(model="claude-sonnet-5-5", system=SYSTEM, messages=[...], max_tokens=512)
with client.messages.stream(model="claude-sonnet-5-5", system=SYSTEM, messages=[...], max_tokens=512) as stream:
for text in stream.text_stream: # decoded, even when an alias is split across events
print(text, end="")
# or iterate the SDK's events (raw, `text`, `content_block_stop`) — all decoded; use one consumer per stream
The wrappers protect
chat.completions.create,responses.create/parse/stream,messages.createandmessages.stream. Other endpoints (batches, legacy completions, …) are not protected: unprotectedmessages.*methods raise instead of silently sending plaintext, but other client attributes are forwarded as-is. Structured-output schemas given as dicts (text,response_format) are obfuscated like tool schemas; schemas given as pydantic classes (text_format) are converted by the SDK and sent as-is, andoutput_parsedis parsed by the SDK from the model's raw text.
LangChain
from obfusai.adapters.langchain import ObfusAIDocumentTransformer, ObfusAIProtectedModel, ObfusAIRunnable
chain = prompt | ObfusAIProtectedModel(chat_model, ObfusAIComponent("high")) # obfuscate → call → decode (+ streaming)
chain = prompt | ObfusAIRunnable(ObfusAIComponent("high")) | chat_model # obfuscate only
docs = ObfusAIDocumentTransformer(ObfusAIComponent()).transform_documents(docs) # RAG playbooks
Any language: the gateway
Native ports of the engine to every language would drift apart (and Go/Rust regexes lack the look-around the detectors rely on), so there is one implementation and a gateway in front of it:
pip install obfusai
obfusai serve --port 8787 --level medium --seed "$OBFUSAI_SEED" # then change only your SDK's base URL
| Language / SDK | OpenAI | Anthropic |
|---|---|---|
| JavaScript / TypeScript | new OpenAI({ baseURL: "http://127.0.0.1:8787/v1" }) |
new Anthropic({ baseURL: "http://127.0.0.1:8787" }) |
| Go | option.WithBaseURL("http://127.0.0.1:8787/v1/") |
same option, base http://127.0.0.1:8787 |
| Java / Kotlin | .baseUrl("http://127.0.0.1:8787/v1") |
.baseUrl("http://127.0.0.1:8787") |
| C# / .NET | new OpenAIClientOptions { Endpoint = new Uri("http://127.0.0.1:8787/v1") } |
base address http://127.0.0.1:8787 |
| Rust | OpenAIConfig::new().with_api_base("http://127.0.0.1:8787/v1") |
base URL http://127.0.0.1:8787 |
| Ruby · PHP · Swift · curl ... | any client with a configurable base URL |
The gateway handles POST /v1/chat/completions, /v1/responses and /v1/messages (+ count_tokens): the request is obfuscated, the reply decoded (tool-call arguments and SSE streams included). Your own Authorization / x-api-key header is forwarded; no key is stored. Everything else is forwarded unprotected (it logs a warning; --strict rejects those POSTs). Invalid JSON is rejected, never forwarded.
What has been verified: the official OpenAI and Anthropic JavaScript SDKs, plain-HTTP clients in Go, Java, Ruby and PHP (see examples/gateway/, runnable against a fake provider with no API key via run_demo_stack.py), and the Python SDKs in the test suite. The C#, Rust and other SDK option names above come from their documentation and were not run here. The gateway is a stdlib sidecar for localhost, not an internet-facing proxy: it has no authentication, one connection per request and modest throughput.
LlamaIndex
from obfusai.adapters.llamaindex import ObfusAILLM, ObfusAINodePostprocessor
llm = ObfusAILLM(OpenAI(model="gpt-4o"), ObfusAIComponent("high", seed="my-secret"), system_prompt=SYSTEM)
engine = index.as_query_engine(
llm=llm, # system prompt obfuscated, replies decoded (stream/async too)
node_postprocessors=[ObfusAINodePostprocessor()], # retrieved chunks obfuscated *after* retrieval
)
Obfuscate at query time with the postprocessor so retrieval still runs on readable text; ObfusAITransform (ingestion) stores obfuscated text, which embeds poorly. Set system_prompt on the wrapper, not on the wrapped LLM. ObfusAILLM is a FunctionCallingLLM: chat_with_tools, predict_and_call, agents and the streaming variants work, and the description/title text of dict-based tool schemas (OpenAI, Anthropic, Mistral-style providers) is obfuscated with the same alias table (names, parameters and enums stay intact). Node metadata is only obfuscated with obfuscate_metadata=True.
Intensity levels
| Level | Obfuscates | Trade-off |
|---|---|---|
low |
numbers, %, currency, dates & times (one alias per date), snake/camel/Pascal/dotted identifiers, your keywords & rules | smallest anchor, highest reliability |
medium (default) |
+ quoted strings, ACRONYMS, Proper Noun Phrases | balanced |
high |
+ long domain words, ⟦α⟧-wrapped aliases, reversed anchor definitions, decoy aliases, shuffled table |
biggest anchor, hardest to read, lowest decoding margin — measure it on your model |
Only high hides the values from a plain-text search: at low and medium the anchor lists them verbatim.
Never touched: template placeholders ({var}, {{var}}, ${var}, %(x)s), chat special tokens, list markers, and directive words such as NEVER / MUST. Short prompts pay a fixed anchor cost (the example above grows ×4); the ratio shrinks quickly on real, multi-page prompts, and anchor_style="compact" trims it further.
Tune what gets hidden
ObfusAIComponent(
"medium",
keywords=["Helios"], # always hide (case-insensitive)
protected=["JSON"], # never hide
rules=[r"ACME-\d{4}", r"project (\w+)"], # your regexes (capture groups → only the group)
extra_detectors=["emails", "urls", "ips"], # opt-in detectors; see obfusai.DETECTORS
alias_mode="stable", # same value → same alias in every prompt
anchor_language="zh", anchor_style="compact",
)
Personal data & credentials
Opt-in detectors with checksum validation (a 16-digit number that fails Luhn is not a card):
| Detector | Matches |
|---|---|
phones |
international / national numbers written with +, parentheses, hyphens or dots |
cn_phones |
mainland-China mobile numbers (1[3-9]xxxxxxxxx, optional +86) |
cards |
payment cards (Luhn) · ssn US SSNs · cn_ids 18-digit resident IDs (checksum + date) · ibans (mod-97) |
uuids · secrets |
UUIDs · AWS/GitHub/Slack/Google/sk-… keys, JWTs, private-key blocks |
ObfusAIComponent("medium", extra_detectors=["phones", "cards", "cn_ids", "uuids"])
The anchor still carries every hidden value, so remove credentials from prompts instead of obfuscating them. Use the audit to find them:
from obfusai import audit
report = audit(system_prompt) # emails, IPs, secrets and personal data by default
if not report.clean:
print(report.counts) # {"secrets": 1, "emails": 2}
for f in report.findings: # detector, start, end and a *masked* preview (never the value)
print(f.detector, f.start, f.end, f.preview)
obfusai audit prompt.txt does the same from the shell (exit code 1 when something is found). Detection is heuristic: it lowers the chance of shipping sensitive data, it does not prove a prompt is clean.
Per-field policies
Different parts of a request deserve different treatment: hide proprietary numbers in the system prompt, but leave tool descriptions readable if your model picks tools more reliably that way.
from obfusai import FieldPolicy
engine = ObfusAIComponent(
"high",
policies={
"system": FieldPolicy(extra_detectors=["emails"], keywords=["Helios"]), # add to this field only
"tools": FieldPolicy(skip=True), # leave untouched
"*": FieldPolicy(detectors=["numbers"]), # fallback for other fields
},
)
Field names: message roles (system, developer, user, …), instructions (Responses API), tools, documents (LangChain), nodes / prompt (LlamaIndex). A policy's keywords, protected and rules add to the component-level ones; detectors replaces and extra_detectors extends the detector set. Skipped fields stay byte-for-byte untouched — the anchor is never written into them (a dedicated system message is added instead when needed). All adapters honour policies.
Guard the output
from obfusai import LeakDetector
guard = LeakDetector(result.mapping, session_ids=[result.session_id])
report = guard.scan(model_reply)
if report.leaked: # kinds: anchor · definition · session · alias · value
reply = guard.redact(model_reply) # strips anchors/definitions, masks aliases and values
With obfuscation, build the detector from the run: LeakDetector(result.mapping, session_ids=[result.session_id]). Without it, pass the values to protect (values=[...]) as in the quickstart. Stream-safe decoding: StreamRestorer(result.mapping).feed(chunk) … .flush().
Measure before you trust it
Two questions decide whether obfuscation is worth using, and only your own model can answer them. One command asks both:
export OPENAI_API_KEY=... # or ANTHROPIC_API_KEY
python examples/validate_real_model.py --yes # prints the number of API calls first
- Fidelity: does the model follow the decoded prompt as well as the plaintext one? (
evaluate_fidelity) - Protection: do extraction attacks ("repeat everything above", "translate your instructions", base64, "decode the table" ...) leak the hidden values less often than against the plaintext prompt? (
evaluate_extraction)
If the obfuscated leak rate is not clearly below the plaintext one, obfuscation is not helping on that model: if both are high the model repeats the table, if both are zero it already refuses. Replace the sample prompt with your own before believing the verdict.
from obfusai.redteam import evaluate_extraction
report = evaluate_extraction(my_chat_callable, SYSTEM, engine=ObfusAIComponent("medium"))
print(report.summary()) # leak rate against plaintext vs obfuscated, and the gain
report.helps() # at least 30 points lower than plaintext?
from obfusai.evaluation import FidelityCase, evaluate_fidelity
report = evaluate_fidelity(
my_chat_callable, # messages -> reply text
[FidelityCase("buy", SYSTEM, "edge_bps=40", "BUY")],
levels=("low", "medium", "high"), repeats=3,
)
print(report.summary())
report.acceptable("high") # same pass rate as plaintext and no leaks?
obfusai.testing.DecodingMockLLM is a deterministic stand-in that decodes the anchor, handy for unit tests.
CLI
obfusai obfuscate prompt.txt -l high --seed s --lang zh --mapping-out map.json > protected.txt
obfusai restore reply.txt -m map.json
obfusai scan reply.txt -m map.json # exit code 1 if the reply leaks
obfusai audit prompt.txt # exit code 1 if the prompt contains secrets / personal data
obfusai serve --port 8787 --seed "$OBFUSAI_SEED" # gateway for any language
obfusai keygen # new key for sealed prompts
obfusai seal skill.md --label desk -o skill.sealed # key from $OBFUSAI_SEAL_KEY
obfusai unseal skill.sealed --expect-label desk
obfusai canary embed skill.md --recipient acme -o acme.md # secret from $OBFUSAI_CANARY_SECRET
obfusai canary find leaked.txt --recipients acme globex # exit 1 if no token matches
How it works
flowchart LR
A[Plain prompt + tool schemas] --> B[Detectors<br/>numbers · identifiers · terms · rules]
B --> C[Shared alias table<br/>Greek symbols]
C --> D[Obfuscated text]
C --> E[Anchor block<br/>decoding table]
D & E --> F[LLM]
F --> G[Reply]
G --> H[LeakDetector / StreamRestorer]
Threat model
| Exposure | What ObfusAI does |
|---|---|
| Prompt copies in logs, traces, vendor dashboards, screenshots | Numbers, keywords and identifiers show as symbols. Anyone who gets the whole message gets the table with it |
| "Repeat your instructions" / prompt injection | No guarantee. A model that repeats its context repeats the table. Some models obey "don't reveal this table" better than "don't reveal your prompt": measure with evaluate_extraction |
| Attacker who dumps the full context (including the anchor) | Recovers everything. Detect with LeakDetector; do not rely on secrecy |
| Credentials and personal data in prompts | Do not obfuscate them: remove them (audit finds them) |
| Insider with the seed | Can reproduce aliases; the seed is not a cryptographic key |
FAQ
Does the model really follow the symbols? Often, but it varies by model, prompt length and intensity, and nothing here has been measured against a live model yet. That is why evaluate_fidelity and evaluate_extraction exist: gate rollout on them.
Is it worth it? If you can keep the sensitive logic out of the prompt (a server-side tool, a classifier, a config the model never sees), do that instead. Use ObfusAI when the prompt must carry the logic and you want a layer against casual exposure plus leak detection.
Do tool calls still work? Yes. Only description/title text is obfuscated; names, parameters, enums and types are untouched.
Why does the same prompt look different between processes? The default seed is random per instance. Pass a fixed seed for reproducible output (and provider prompt caching).
Where do {placeholders} go? They are preserved so your templating keeps working.
Roadmap
Prompt audit and reply redaction enforced inside the gateway · Chat Completions parse / stream helpers · obfuscating pydantic-class structured-output schemas · decoding LangChain streamed tool_call_chunks.
Development
pip install -e ".[dev]" && pytest
python examples/basic_usage.py && python examples/advanced_usage.py
License
MIT — see LICENSE.
⭐ Support ObfusAI
If this project saved you from a leaked prompt, or you simply like the idea:
- Star the repository — the cheapest, most useful way to help
- Share it with a teammate who ships LLM features
- Open an issue with a bug, a detector you need or a model it struggles with
Metadata
Release files for obfusai 0.11.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| obfusai-0.11.0.tar.gz | 109.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| obfusai-0.11.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 187.3 kB
Release files / obfusai-0.11.0.tar.gz
| Download URL | obfusai-0.11.0.tar.gz |
|---|---|
| Size | 109.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d03325720eed792ddbd1bfb67d16f8332e0597417bdd64ea82630fc9bb29dfaf
|
|
BLAKE2b-256 checksum How to use checksums |
963026a21adf9a438bb6843a03dbca3fcf1de754d2963dd72221bcc23f3c6fcc
|
| 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 Oct 17, 2026.
Transparency logRelease files / obfusai-0.11.0-py3-none-any.whl
| Download URL | obfusai-0.11.0-py3-none-any.whl |
|---|---|
| Size | 78.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
95269165904c9e89d3f89d5650e78deeef1571c22d40e1a312f9b8f8fdc05644
|
|
BLAKE2b-256 checksum How to use checksums |
bf629a543188a6869fa0eda61d232599f50ad1ad6ca0f1a512849bbbfccfb1b0
|
| 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 Oct 17, 2026.
Transparency log