Maison >développement back-end >Tutoriel Python >Masquage des données confidentielles dans les invites à l'aide de Regex et spaCy

Masquage des données confidentielles dans les invites à l'aide de Regex et spaCy

Barbara Streisand
Barbara Streisandoriginal
2024-12-05 04:07:08536parcourir

Masking confidential data in prompts using Regex and spaCy

Les gens ont des problèmes de confidentialité concernant les LLM populaires comme OpenAI, Gemini, Claude etc...,. On ne sait pas vraiment ce qui se passe derrière les écrans, sauf s'il s'agit d'un modèle open source. Nous devons donc être prudents de notre côté.

La première chose serait le traitement des informations que nous transmettons aux LLM. Les experts recommandent d’éviter d’inclure des informations confidentielles ou des identifiants personnels dans les invites. Cela semble plus simple, mais à mesure que la taille du contexte des LLM augmente, nous pouvons transmettre des textes volumineux aux modèles. Cela pourrait donc devenir difficile à vérifier et masquer tous les identifiants. 

J'ai donc essayé de créer un script python qui détecterait et masquerait les identifiants et les informations confidentielles. Regex est magique et mis en œuvre pour reconnaître différentes informations confidentielles et les remplacer par des masques. Également utilisé la bibliothèque Spacy pour détecter les identifiants courants tels que le nom, le lieu, etc.,

Remarque : pour le moment, cela convient au contexte indien, mais un identifiant commun peut toujours être détecté. 

Regardons donc la mise en œuvre (j'ai pris l'aide de LLM pour la mise en œuvre)
Si vous souhaitez ignorer l'explication. 

Voici le lien vers la base de code : aditykris/prompt-masker-Indian-context
Importer les modules/bibliothèques nécessaires

import re 

from typing import Dict, List, Tuple

import spacy

nlp = spacy.load("en_core_web_sm")

Vous devez installer manuellement "en_core_web_sm" à l'aide de l'extrait ci-dessous

python -m spacy télécharger en_core_web_sm

Définition des informations confidentielles indiennes communes.

class IndianIdentifier:
    '''Regex for common Indian identifiers'''
    PAN = r'[A-Z]{5}[0-9]{4}[A-Z]{1}'
    AADHAR = r'[2-9]{1}[0-9]{3}\s[0-9]{4}\s[0-9]{4}'
    INDIAN_PASSPORT = r'[A-PR-WYa-pr-wy][1-9]\d\s?\d{4}[1-9]'
    DRIVING_LICENSE = r'(([A-Z]{2}[0-9]{2})( )|([A-Z]{2}-[0-9]{2}))((19|20)[0-9][0-9])[0-9]{7}'
    UPI_ID = r'[\.\-a-z0-9]+@[a-z]+'
    INDIAN_BANK_ACCOUNT = r'\d{9,18}'
    IFSC_CODE = r'[A-Z]{4}0[A-Z0-9]{6}'
    INDIAN_PHONE_NUMBER = r'(\+91|\+91\-|0)?[789]\d{9}'
    EMAIL = r'[\w\.-]+@[\w\.-]+\.\w+'

    @classmethod
    def get_all_patterns(cls) -> Dict[str, str]:
        """Returns all regex patterns defined in the class"""
        return {
            name: pattern 
            for name, pattern in vars(cls).items() 
            if isinstance(pattern, str) and not name.startswith('_')
        }

Donc, je révisais les classes et les méthodes Python, alors j'ai commencé à les implémenter ici. 
J'ai trouvé que l'expression régulière de ces identifiants de DebugPointer était très utile.
Passons maintenant à la fonction de détection. Un simple re.finditer() a été utilisé pour parcourir différents modèles afin de trouver des correspondances. Les correspondances sont stockées dans une liste.

def find_matches(text: str, pattern: str) -> List[Tuple[int, int, str]]:
    """
    Find all matches of a pattern in text and return their positions and matched text
    """
    matches = []
    for match in re.finditer(pattern, text):
        matches.append((match.start(), match.end(), match.group()))
    return matches

Utilisation d'un simple dictionnaire pour stocker les textes de remplacement. Enveloppé dans une fonction pour renvoyer le texte de remplacement.

def get_replacement_text(identifier_type: str) -> str:
    """
    Returns appropriate replacement text based on the type of identifier
    """
    replacements = {
        'PAN': '[PAN_NUMBER]',
        'AADHAR': '[AADHAR_NUMBER]',
        'INDIAN_PASSPORT': '[PASSPORT_NUMBER]',
        'DRIVING_LICENSE': '[DL_NUMBER]',
        'UPI_ID': '[UPI_ID]',
        'INDIAN_BANK_ACCOUNT': '[BANK_ACCOUNT]',
        'IFSC_CODE': '[IFSC_CODE]',
        'INDIAN_PHONE_NUMBER': '[PHONE_NUMBER]',
        'EMAIL': '[EMAIL_ADDRESS]',
        'PERSON': '[PERSON_NAME]',
        'ORG': '[ORGANIZATION]',
        'GPE': '[LOCATION]'
    }
    return replacements.get(identifier_type, '[MASKED]')

Ah ! la partie principale commence.

def analyze_identifiers(text: str) -> Tuple[str, Dict[str, List[str]]]:
    """
    Function to identify and hide sensitive information.
    Returns:
        - masked_text: Text with all sensitive information masked
        - found_identifiers: Dictionary containing all identified sensitive information
    """
    # Initialize variables
    masked_text = text
    found_identifiers = {}
    positions_to_mask = []

    # First, find all regex matches
    for identifier_name, pattern in IndianIdentifier.get_all_patterns().items():
        matches = find_matches(text, pattern)
        if matches:
            found_identifiers[identifier_name] = [match[2] for match in matches]
            positions_to_mask.extend(
                (start, end, identifier_name) for start, end, _ in matches
            )

    # Then, process named entities using spaCy
    doc = nlp(text)
    for ent in doc.ents:
        if ent.label_ in ["PERSON", "ORG", "GPE"]:
            positions_to_mask.append((ent.start_char, ent.end_char, ent.label_))
            if ent.label_ not in found_identifiers:
                found_identifiers[ent.label_] = []
            found_identifiers[ent.label_].append(ent.text)

    # Sort positions by start index in reverse order to handle overlapping matches
    positions_to_mask.sort(key=lambda x: x[0], reverse=True)

    # Apply masking
    for start, end, identifier_type in positions_to_mask:
        replacement = get_replacement_text(identifier_type)
        masked_text = masked_text[:start] + replacement + masked_text[end:]

    return masked_text, found_identifiers

Cette fonction prend l'invite en entrée et renvoie l'invite masquée ainsi que les éléments identifiés comme dictionnaire.

Laissez-moi vous expliquer un par un.

Boucle suivante à travers les regex de différents identifiants pour trouver la correspondance dans l'invite. S'il est trouvé, alors il :
 1. Stockez les informations identifiées dans un dictionnaire avec le type d'identifiant comme clé pour garder une trace.
 2. Note les positions et les stocke dans positions_to_mask afin que nous puissions appliquer le masquage plus tard.

import re 

from typing import Dict, List, Tuple

import spacy

nlp = spacy.load("en_core_web_sm")

Maintenant, c'est l'heure de l'espace. C'est une excellente bibliothèque pour les tâches de traitement du langage naturel (NLP). Nous pouvons extraire les identifiants du texte en utilisant le module nlp.
Actuellement, je l'ai utilisé pour détecter le nom, l'organisation et les emplacements.
Cela fonctionne comme la même boucle ci-dessus pour identifier et stocker l'emplacement.

class IndianIdentifier:
    '''Regex for common Indian identifiers'''
    PAN = r'[A-Z]{5}[0-9]{4}[A-Z]{1}'
    AADHAR = r'[2-9]{1}[0-9]{3}\s[0-9]{4}\s[0-9]{4}'
    INDIAN_PASSPORT = r'[A-PR-WYa-pr-wy][1-9]\d\s?\d{4}[1-9]'
    DRIVING_LICENSE = r'(([A-Z]{2}[0-9]{2})( )|([A-Z]{2}-[0-9]{2}))((19|20)[0-9][0-9])[0-9]{7}'
    UPI_ID = r'[\.\-a-z0-9]+@[a-z]+'
    INDIAN_BANK_ACCOUNT = r'\d{9,18}'
    IFSC_CODE = r'[A-Z]{4}0[A-Z0-9]{6}'
    INDIAN_PHONE_NUMBER = r'(\+91|\+91\-|0)?[789]\d{9}'
    EMAIL = r'[\w\.-]+@[\w\.-]+\.\w+'

    @classmethod
    def get_all_patterns(cls) -> Dict[str, str]:
        """Returns all regex patterns defined in the class"""
        return {
            name: pattern 
            for name, pattern in vars(cls).items() 
            if isinstance(pattern, str) and not name.startswith('_')
        }

Dans certains cas de tests, j'ai remarqué que certains masques manquaient et cela était principalement dû au chevauchement des identifiants. Ainsi, le tri dans l'ordre inverse a aidé à le résoudre.

 

def find_matches(text: str, pattern: str) -> List[Tuple[int, int, str]]:
    """
    Find all matches of a pattern in text and return their positions and matched text
    """
    matches = []
    for match in re.finditer(pattern, text):
        matches.append((match.start(), match.end(), match.group()))
    return matches

Ensuite, nous masquons ce qui se passe en utilisant les données de found_identifiers et positions_to_mask.

def get_replacement_text(identifier_type: str) -> str:
    """
    Returns appropriate replacement text based on the type of identifier
    """
    replacements = {
        'PAN': '[PAN_NUMBER]',
        'AADHAR': '[AADHAR_NUMBER]',
        'INDIAN_PASSPORT': '[PASSPORT_NUMBER]',
        'DRIVING_LICENSE': '[DL_NUMBER]',
        'UPI_ID': '[UPI_ID]',
        'INDIAN_BANK_ACCOUNT': '[BANK_ACCOUNT]',
        'IFSC_CODE': '[IFSC_CODE]',
        'INDIAN_PHONE_NUMBER': '[PHONE_NUMBER]',
        'EMAIL': '[EMAIL_ADDRESS]',
        'PERSON': '[PERSON_NAME]',
        'ORG': '[ORGANIZATION]',
        'GPE': '[LOCATION]'
    }
    return replacements.get(identifier_type, '[MASKED]')

Un exemple d'entrée de ce programme serait :

Entrée :

def analyze_identifiers(text: str) -> Tuple[str, Dict[str, List[str]]]:
    """
    Function to identify and hide sensitive information.
    Returns:
        - masked_text: Text with all sensitive information masked
        - found_identifiers: Dictionary containing all identified sensitive information
    """
    # Initialize variables
    masked_text = text
    found_identifiers = {}
    positions_to_mask = []

    # First, find all regex matches
    for identifier_name, pattern in IndianIdentifier.get_all_patterns().items():
        matches = find_matches(text, pattern)
        if matches:
            found_identifiers[identifier_name] = [match[2] for match in matches]
            positions_to_mask.extend(
                (start, end, identifier_name) for start, end, _ in matches
            )

    # Then, process named entities using spaCy
    doc = nlp(text)
    for ent in doc.ents:
        if ent.label_ in ["PERSON", "ORG", "GPE"]:
            positions_to_mask.append((ent.start_char, ent.end_char, ent.label_))
            if ent.label_ not in found_identifiers:
                found_identifiers[ent.label_] = []
            found_identifiers[ent.label_].append(ent.text)

    # Sort positions by start index in reverse order to handle overlapping matches
    positions_to_mask.sort(key=lambda x: x[0], reverse=True)

    # Apply masking
    for start, end, identifier_type in positions_to_mask:
        replacement = get_replacement_text(identifier_type)
        masked_text = masked_text[:start] + replacement + masked_text[end:]

    return masked_text, found_identifiers

Sortie :
Texte masqué :

for identifier_name, pattern in IndianIdentifier.get_all_patterns().items():
        matches = find_matches(text, pattern)
        if matches:
            found_identifiers[identifier_name] = [match[2] for match in matches]
            positions_to_mask.extend(
                (start, end, identifier_name) for start, end, _ in matches
            )

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn