跳到正文
DEV Community· jamilxt·· 2 小時前AI 評分66

AI 對話分享前如何用 Python 建立私隱檢查閘門

Before You Share an AI Chat, Run This Privacy Gate

AI 導讀

教程示範如何用 Python 建立 AI 對話分享前檢查閘門,偵測電郵、電話、類 API key 字串及私鑰區塊,將匹配內容替換為標記並另存審核副本。工具不會把資料上傳至第三方服務,但只涵蓋有限模式,不能判定剩餘內容安全,分享前仍須由人檢查完整內容。

正文

An AI conversation can look harmless when you read it one message at a time. The full thread may contain a customer name, a draft contract, a private URL, a partial account number, or an internal decision that was never meant for a wider audience.

That risk became concrete in October when researchers reported that they could discover thousands of Meta Muse conversation-sharing links. Ars Technica reported that some pages contained business and personal details, and that Meta said it had paused the sharing feature while it reviewed the issue. This was a report about a particular link-discovery design, not evidence that every AI chat-sharing product works the same way or that every Muse user was exposed.[1]

The useful lesson is broader than one product: a share link is a disclosure boundary. Before making a conversation available to someone else, inspect the actual content and remove details that the recipient does not need.

This tutorial builds a small Python pre-share gate. It catches a few recognizable patterns, replaces them with review markers, writes a separate sanitized copy, and refuses to treat automated redaction as approval. It is a teaching example, not a complete privacy scanner.

Why a last-minute glance is not enough

Context accumulates. A user may paste an error log, then add a customer name, then ask for a summary that repeats those details. The final answer can expose information that was several turns earlier.

Sensitive data is not just a password. OWASP's 2025 guidance identifies personal, financial, health, business, security, and legal information as sensitive categories that can be disclosed through LLM applications.[2] A transcript can include names, contact details, invoice amounts, medical context, source snippets, or credentials. Some of these are easy to recognize with patterns. Others depend on context.

A link may travel farther than intended. Depending on the product, anyone with a link may be able to open the shared page, the page may be indexed or discoverable through infrastructure, and recipients may copy or forward it. Those are distinct risks. Do not assume that a hard-to-guess URL is access control, and do not assume that a vendor's pause or fix applies to other services.

Meta's launch material described Muse as an agent designed to work with personal accounts and data. That makes its share incident a useful reminder to separate two questions: what an assistant can access to perform a task, and what a share feature publishes for another person to see.[3]

The design: detect, redact, review, then share

The gate below has four deliberate limits:

  • It detects a small set of formats using regular expressions.
  • It replaces matches with labels, preserving enough structure to review what was removed.
  • It writes to a new file instead of overwriting the original transcript.
  • It prints a warning and requires a human to review the output before sharing.

It does not upload data to a third-party service. It does not prove that the remaining text is safe. It cannot reliably identify confidential business context, every secret format, or every personal detail.

Save this as share_gate.py:

from __future__ import annotations

import re
import sys
from pathlib import Path

# These patterns are intentionally narrow examples, not universal validators.
RULES: list[tuple[str, re.Pattern[str]]] = [
    (
        "email",
        re.compile(r"\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b", re.I),
    ),
    (
        "phone",
        re.compile(r"(?<!\w)(?:\+?\d[\d ().-]{7,}\d)(?!\w)"),
    ),
    (
        "api_key_like",
        re.compile(r"\b(?:sk|pk|api)[-_][A-Za-z0-9_-]{16,}\b", re.I),
    ),
    (
        "private_key_block",
        re.compile(
            r"-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----"
            r".*?-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----",
            re.S,
        ),
    ),
]


def redact(text: str) -> tuple[str, dict[str, int]]:
    counts: dict[str, int] = {}
    result = text

    for label, pattern in RULES:
        result, count = pattern.subn(f"[REDACTED:{label}]", result)
        if count:
            counts[label] = count

    return result, counts


def main() -> int:
    if len(sys.argv) != 3:
        print("Usage: python share_gate.py INPUT.txt OUTPUT.txt", file=sys.stderr)
        return 2

    source = Path(sys.argv[1])
    destination = Path(sys.argv[2])

    if not source.is_file():
        print(f"Input does not exist: {source}", file=sys.stderr)
        return 2
    if source.resolve() == destination.resolve():
        print("Refusing to overwrite the input file.", file=sys.stderr)
        return 2
    if destination.exists():
        print(f"Output already exists: {destination}", file=sys.stderr)
        return 2

    cleaned, counts = redact(source.read_text(encoding="utf-8"))
    destination.write_text(cleaned, encoding="utf-8")

    print(f"Wrote review copy: {destination}")
    if counts:
        for label, count in sorted(counts.items()):
            print(f"Review {count} match(es): {label}")
    else:
        print("No configured patterns matched. This is not a clean bill of health.")

    print("Do not share until a person reviews the complete output.")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

Run it on an exported plain-text transcript:

python share_gate.py conversation.txt conversation.review.txt

The first version intentionally does not read every export format. If a product exports HTML, JSON, PDF, or images, convert or extract the content through a trusted process first, then inspect the result. A failed conversion can omit content without making the original safe to share.

Test the gate with known examples

Create a disposable test input, not a real customer transcript:

Contact: alex@example.com
Notes: call the sample number +1 (202) 555-0147

Run the command and inspect conversation.review.txt. The email and phone-shaped sequence should become markers. The test suite below constructs a synthetic token-like value and checks that pattern separately. The scanner reports the number of matches by category, not the original values, so the terminal output does not repeat what it detected.

The phone expression is deliberately broad. It may redact dates, ticket numbers, or other digit sequences. That is safer than pretending a simple pattern can distinguish every phone number from every other number, but it can make a review copy harder to read. Adjust it for your environment and add tests for both the values you want caught and examples you do not want changed.

Add a private-key test in a separate dummy file if needed. Never paste a real credential into a test fixture. For a real key discovered in a transcript, treat it as exposed: revoke or rotate it at the system that issued it. Redaction changes the copy; it does not undo disclosure.

Add a test suite before relying on changes

The replacement function is easy to exercise without installing packages. In the test, construct a synthetic key-like value from string fragments so your editor or secret scanner does not mistake the fixture for a real credential. Put this in test_share_gate.py next to the script:

import unittest

from share_gate import redact


class RedactionTests(unittest.TestCase):
    def test_email_is_replaced(self):
        result, counts = redact("Contact alex@example.com for details")
        self.assertIn("[REDACTED:email]", result)
        self.assertNotIn("alex@example.com", result)
        self.assertEqual(counts["email"], 1)

    def test_key_like_value_is_replaced(self):
        value = "sk-demo_" + "x" * 20
        result, counts = redact("token " + value)
        self.assertIn("[REDACTED:api_key_like]", result)
        self.assertEqual(counts["api_key_like"], 1)

    def test_private_key_block_is_removed_as_one_match(self):
        begin = "-----BEGIN " + "PRIVATE KEY-----"
        end = "-----END " + "PRIVATE KEY-----"
        text = "before\n" + begin + "\ndummy-material\n" + end + "\nafter"
        result, counts = redact(text)
        self.assertIn("[REDACTED:private_key_block]", result)
        self.assertNotIn("dummy-material", result)
        self.assertEqual(counts["private_key_block"], 1)

    def test_unmatched_text_is_preserved(self):
        text = "The service returned status 204."
        result, counts = redact(text)
        self.assertEqual(result, text)
        self.assertEqual(counts, {})


if __name__ == "__main__":
    unittest.main()

Run the tests with:

python -m unittest -v

A passing test only means the examples behave as written. It does not measure recall against real-world secrets or privacy leaks. Build a local test corpus with synthetic values that represent your organization's formats, and have a security or privacy owner review both false negatives and false positives.

Where pattern matching stops

Names and confidential facts need context. A regular expression can spot many email addresses. It cannot know that “Project Cedar” is confidential, that a meeting time identifies an unreleased acquisition, or that a combination of public details points to one person.

Use a detector as another layer, not the final authority. Microsoft Presidio's documentation describes separate detection and anonymization stages and supports recognizers and replacement operators. Detection is configurable, which means results depend on the recognizers and data you provide.[4] A more complete pipeline can send text to a local or approved analyzer, redact detected entities, then show a reviewer what changed. That still needs human review and a threat model.

Don't ask the model to police itself. A prompt such as “remove sensitive information” can help with drafting, but it is not a security boundary. OWASP warns that relying on model instructions alone can fail because model behavior is unpredictable and may be circumvented.[2] Treat model-based redaction as an assistive step, never as the only gate.

Keep the original protected. The sample writes a second file and refuses to overwrite either input or an existing output. In a production workflow, also restrict file permissions, avoid logging transcript contents, define retention and deletion, and decide whether backups include both files.

Turn it into a product workflow

If a team is adding conversation sharing to an internal tool, make the privacy gate part of the share action rather than a separate reminder:

  1. Preview the exact artifact. Show the full content and attachments that the link will expose, not only the latest assistant answer.
  2. Scan for known sensitive patterns. Check credentials, contact details, account identifiers, and organization-specific markers.
  3. Mark uncertain content. Use labels such as “possible customer data” rather than silently deleting whole paragraphs.
  4. Require a deliberate confirmation. The person sharing should confirm the recipient and whether the link is public, restricted, or time-limited.
  5. Set an expiration and revoke path. If the product cannot revoke or expire a link, disclose that before creating it.
  6. Audit access safely. Record the share event and access metadata without copying the sensitive transcript into routine logs.
  7. Test discovery, not just guessing. Evaluate whether unintended people can find or access links through search, logs, referrers, previews, caches, or infrastructure metadata.

These controls address different failure modes. A scanner does not fix public link enumeration. Authentication does not prevent a user from sharing the wrong conversation. Link expiration does not erase screenshots or copies. Defense comes from making the scope clear and limiting access at more than one point.

A pre-share checklist

Before sending an AI conversation link, check:

  • Purpose: Does the recipient need the whole conversation, or only a short excerpt?
  • Content: Did you inspect prompts, responses, uploaded files, generated artifacts, and tool outputs?
  • Secrets: Are credentials, private URLs, tokens, or account details present? If a live secret appeared, rotate it.
  • People: Could names, contact details, schedules, or combined facts identify someone?
  • Business: Does the thread include customer information, unreleased plans, financial details, or internal commentary?
  • Access: Who can open the link, and can it be revoked or expired?
  • Review: Has a person checked the final version that will actually be shared?

The important distinction is between “the scanner found nothing” and “a reviewer confirmed the intended content is safe for this audience.” The first is a tool result. The second is a decision.

The takeaway

AI conversation sharing is useful when the shared scope is intentional. But the conversation may contain more than the piece you meant to show, and the URL itself may not enforce the audience you imagined.

A small pre-share gate can catch obvious patterns and create a safer review copy. Its value is not that regular expressions solve privacy. Its value is that it turns “share” from a reflex into a controlled step: inspect, redact, review, confirm, then publish only what the recipient needs.

I write practical guides about AI infrastructure, developer tools, and secure software workflows. Subscribe for future tutorials and comparisons.

Have you added a review step before sharing AI-generated content or conversation links? What would you want the scanner to flag automatically, and what should always stay a human decision?

Sources

[1] Ars Technica: Meta Muse share links
[2] OWASP: Sensitive Information Disclosure
[3] Meta: How We Built Safety Into Muse
[4] Microsoft Presidio: Text Anonymization

來源:DEV Community · dev.to