bubble-shield-anonymize — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited bubble-shield-anonymize (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
The bubble-shield hook blocks reads of protected client folders because raw identifying data must never enter the model context in clear. This skill is the sanctioned path: anonymise locally first, then work on the cloaked copy.
If a tool call was denied with a 🔒 Bubble Shield message, do NOT try to bypass it (no cat, no copying the file elsewhere, no reading via Bash).
bubble_shield_read (one file)The plugin ships an MCP tool `bubble_shield_read` (namespaced, e.g. mcp__plugin_bubble-shield_bubble_shield__bubble_shield_read, or just call it bubble_shield_read). Give it the blocked file's path and it returns the file's contents already anonymised (⟦NOM_0001⟧-style tokens) — the real names/IBANs/e-mails never enter your context. This is the preferred way to read a single protected file, especially in Cowork, where it's the mechanism that actually works (a normal Read of a protected file is blocked by design; bubble_shield_read is the sanctioned read).
bubble_shield_read(path="~/Dossiers-clients/dossier-dupont/contrat.pdf")
→ returns the cloaked text; work on THAT.It handles .pdf/.docx/.txt/.md/.csv/.json, uses the same vault as the rest of Bubble Shield (so tokens are consistent and reversible), and fails closed (returns an error, never raw text, if it can't anonymise). When your answer still carries ⟦…⟧ tokens, de-anonymise it locally (see "De-anonymise the answer" below).
clean/For a whole dossier (many files at once), or when you want anonymised copies on disk, use the batch flow instead:
clean/ sub-folder.⟦NOM_0001⟧,de-anonymise it locally for the user with the same vault.
The plugin is fully self-contained — it bundles the Bubble Shield engine and a pure-python PDF reader under vendor/, so it runs from a GitHub install or a Cowork zip with no `pip install`, no engine on the user's machine, no network. Just Python 3.10+ (already present on any Mac).
PDFs and plain files work out of the box. scripts/bubble_shield_extract.py turns a .pdf (and .txt/.md/.csv/.json) into text before anonymising — one command covers a whole dossier. .docx is the one format that still needs an extra lib (pip install python-docx); a scanned/image-only or encrypted PDF fails closed (raises, never returns empty) — extract its text by hand, never wave it through.
# Whole dossier → cloaked copies + one shared vault. PDFs auto-extracted.
python3 - <<'PY'
import os, sys
from pathlib import Path
# Self-contained: the plugin bundles its deps under vendor/ (the engine + pypdf).
# CLAUDE_PLUGIN_ROOT is exported by Claude Code while the plugin is active.
PLUGIN_ROOT = Path(os.environ.get("CLAUDE_PLUGIN_ROOT", Path(__file__).resolve().parent))
sys.path.insert(0, str(PLUGIN_ROOT / "vendor")) # bundled bubble_shield engine + pypdf
sys.path.insert(0, str(PLUGIN_ROOT / "scripts")) # bundled extractor
from bubble_shield_extract import extract_file, ExtractionError
from bubble_shield import AnonymizationEngine, Vault
SRC = Path("~/Dossiers-clients/dossier-dupont").expanduser() # the protected folder
OUT = SRC / "clean" # allow-listed output dir
OUT.mkdir(exist_ok=True)
vault = Vault(mission=SRC.name) # ONE shared vault per dossier → same client = same token everywhere
engine = AnonymizationEngine(vault=vault)
PATTERNS = ("*.txt", "*.md", "*.csv", "*.json", "*.pdf", "*.docx")
for pat in PATTERNS:
for f in sorted(SRC.glob(pat)):
try:
text = extract_file(f) # PDF/.docx → text; plain files decode
except ExtractionError as e:
print(f"⛔ {f.name}: {e} — SKIP (extrais le texte à la main, ne le laisse pas passer)")
continue
res = engine.anonymize(text)
(OUT / f"{f.stem}.anon.txt").write_text(res.anonymized, encoding="utf-8")
if not res.safe_to_send:
print(f"⚠️ {f.name}: {res.verdict_fr} — relecture humaine requise")
vault.save_encrypted(str(SRC / ".vault.enc"), passphrase="<set-by-operator>") # coffre chiffré, reste local
print("done — clean/ contains the cloaked copies; the vault never leaves this machine")
PYIf ${CLAUDE_PLUGIN_ROOT} isn't set in your shell (e.g. running the snippet by hand), point sys.path at the plugin's scripts/ dir directly, or call the extractor as a CLI: python3 <plugin>/scripts/bubble_shield_extract.py <file.pdf>.
Then read clean/*.anon.txt (the clean/ sub-folder should be in the guard's allow_paths, or .anon.txt in allow_extensions, so it's readable).
When you've drafted a summary/letter that still contains ⟦TYPE_NNNN⟧ tokens, restore the real values locally before handing it to the user:
from bubble_shield import AnonymizationEngine, Vault
vault = Vault.load_encrypted("~/Dossiers-clients/dossier-dupont/.vault.enc", passphrase="...")
engine = AnonymizationEngine(vault=vault)
print(engine.deanonymize(draft_with_tokens))When the user wants to see what gets masked vs kept — the before/after, the verdict, and the masquer/conserver table — generate a Bubble Shield artifact and present it. This is the Cowork equivalent of the local webapp (which can't run in Cowork's sandbox: its server binds to the VM's localhost, not the user's screen).
# Generates one self-contained HTML file (same view + styling as the webapp).
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/make_artifact.py \
--file "<a dossier file>" --mission "<dossier name>" \
--out "<a writable path, e.g. the session outputs dir>/bubble-shield-apercu.html"
# or feed text directly: --text "…"Then present that HTML file to the user as an artifact (Cowork: present_files / create_artifact with the generated file). It renders on their screen with the before/after columns, the verdict, and the masquer/conserver toggle table.
The masquer/conserver toggles reflect the current policy. The artifact is sandboxed HTML so it can't write to disk itself — when the user wants to change a setting ("conserve les montants", "masque le poste"), YOU update the policy (bubble_shield.policy.save_policy) and re-run, then re-present the artifact. Same outcome as clicking save in the webapp.
For a non-technical user demo, never use real client data — use a fictional sample (the engine has none baked in; make up a plausible "Jean Dupont" record).
safe_to_send is false, flag it — do not treat the doc as safe.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.