feat: secrets masqués — couverture universelle clés API/mots de passe + clic pour copier (#188)
CI / lint (push) Successful in 2m40s
CI / security (push) Successful in 1m33s
CI / test (push) Successful in 4m19s
CI / build (push) Successful in 1m31s
CI / e2e (push) Successful in 16m56s

This commit is contained in:
2026-10-08 13:22:27 -04:00
parent 634ba8a272
commit e01e837a2a
20 changed files with 520 additions and 65 deletions
+89
View File
@@ -845,6 +845,95 @@ class TestSecretRedactor:
text = f"value {blob} end"
assert redact_file_content(text) == text
# ── Feature #188 : couverture universelle + clic → copie ──────────
@pytest.mark.parametrize("secret", [
"sk-" + "a" * 30, # OpenAI / OpenRouter
"sk_live_" + "b" * 24, # Stripe
"whsec_" + "c" * 24, # Stripe / Svix
"glpat-" + "d" * 24, # GitLab
"github_pat_" + "e" * 30, # GitHub fine-grained
"npm_" + "f" * 36, # npm
"dckr_pat_" + "g" * 24, # Docker Hub
"hf_" + "h" * 32, # Hugging Face
"AIza" + "I" * 35, # Google API key
"ya29." + "J" * 25, # Google OAuth
"xoxb-" + "K" * 24, # Slack
"SG." + "L" * 24, # SendGrid
"re_" + "M" * 40, # Resend
"sq0atp-" + "N" * 24, # Square
"ATATT" + "O" * 24, # Atlassian
"AKIA" + "P" * 16, # AWS
"ASIA" + "Q" * 16, # AWS STS
"ghp_" + "R" * 36, # GitHub legacy
"123456789:AA" + "S" * 33, # Telegram bot (35 chars after ":")
"Bearer " + "T" * 32, # Authorization header
])
def test_universal_provider_keys_masked(self, secret):
"""#188 — the large majority of token formats end up masked."""
from backend.secret_redactor import redact_file_content
result = redact_file_content(f"here: {secret}")
assert secret not in result
assert "MASQUÉ" in result
@pytest.mark.parametrize("text", [
"password=hunter2",
"PASSWORD=short",
"db_password=abc12345",
'password: "quoted1"',
'{"passwd": "jsonpass"}',
"passphrase=longenough",
"mot de passe=chezmoi",
"user_pwd=s3cret",
"token=abcdef12", # 8+ char generic values are masked too
])
def test_passwords_masked_any_length(self, text):
"""#188 — passwords are masked whatever their length / quoting."""
from backend.secret_redactor import redact_file_content
result = redact_file_content(text)
assert "MASQUÉ" in result
# No raw value left behind
assert text.split("=", 1)[-1].split(":", 1)[-1].strip('"\' ') not in result
def test_pwd_env_var_not_masked(self):
"""``PWD=`` is a shell working directory, not a password."""
from backend.secret_redactor import redact_file_content
text = "PWD=/home/bruno/notes"
assert redact_file_content(text) == text
def test_placeholders_restore_clickable_mask(self):
"""#188 — placeholders become spans carrying the real value."""
from backend.secret_redactor import redact_with_placeholders, restore_masks
secret = "sk-" + "a" * 30
text, entries = redact_with_placeholders(f"key: {secret}")
assert secret not in text and entries
html = restore_masks(text, entries, click_to_copy=True)
assert f'data-secret="{secret}"' in html
assert 'class="secret-mask"' in html
# Plain restore never leaks the value
plain = restore_masks(text, entries)
assert secret not in plain and "data-secret" not in plain
def test_render_markdown_masks_clickable_in_app(self):
"""#188 — the authenticated preview serves clickable masks."""
from backend.render import _render_markdown
secret = "sk-" + "a" * 30
md = f"```bash\nexport KEY={secret}\n```\n"
html = _render_markdown(md, "test_vault", None, click_to_copy=True)
# The value only ever appears inside the data-secret attribute
assert 'class="secret-mask"' in html
assert f'data-secret="{secret}"' in html
assert f">{secret}" not in html # never as visible text
def test_render_markdown_plain_without_click(self):
"""Public shares / PDF exports get the plain label only."""
from backend.render import _render_markdown
secret = "sk-" + "a" * 30
html = _render_markdown(f"Cle: {secret}\n", "test_vault", None)
assert secret not in html
assert "[CLÉ API MASQUÉE]" in html
assert "data-secret" not in html
# ═══════════════════════════════════════════════════════════════════
# Static / PWA caching (Cloudflare / mobile freshness)