Salvaguarda RGPD: ocultar NIF, telefone e IBAN
Salvaguarda RGPD que oculta NIF (com verificação), telefones +351, IBAN PT e e-mails pessoais nas respostas de um assistente de IA. 12 testes.
Regras
Ocultar antes de qualquer envio todos os dados que identifiquem ou permitam contactar uma pessoa: - endereços de e-mail pessoais (ines.ferreira@…); - números de telefone portugueses, com ou sem +351; - IBAN portugueses (PT50…); - NIF (verifica-se o dígito de controlo). Deixar passar: endereços genéricos (geral@, info@, apoio@…), nomes de empresas, montantes, datas, números de encomenda.
Casos de teste
| Entrada | Esperado |
|---|---|
| Pode escrever para ines.ferreira@gmail.com para novidades. | Bloquear |
| O meu telemóvel é o 912 345 678. | Bloquear |
| Ligue-me para o +351 21 123 4567 amanhã. | Bloquear |
| Transferência para PT50 0002 0123 1234 5678 9015 4. | Bloquear |
| NIF: 123456789. | Bloquear |
| Fatura a enviar para joao.santos@empresa.pt. | Bloquear |
| Escreva para apoio@contextetech.com para qualquer pergunta. | Permitir |
| A sua encomenda n.º 458712 sai amanhã. | Permitir |
| O montante é de 1250,00 € com IVA incluído. | Permitir |
| Marcação a 12/10/2026 às 14h. | Permitir |
| A empresa Construções Silva, Lda. aceitou o orçamento. | Permitir |
| Bom dia, em que posso ajudar? | Permitir |
Código
import re
PADROES = {
"email": r"[\w.+-]+@[\w-]+\.[\w.-]+",
"telefone": r"(?:\+351[\s.-]?)?\b(?:9[1236]\d[\s.-]?\d{3}[\s.-]?\d{3}|2\d[\s.-]?\d{3}[\s.-]?\d{4}|2\d{2}[\s.-]?\d{3}[\s.-]?\d{3})\b",
"iban": r"\bPT50(?:\s?\d{4}){5}\s?\d\b",
"nif": r"\b[1235689]\d{8}\b",
}
GENERICOS = ("geral@", "info@", "apoio@", "suporte@", "contacto@")
def nif_valido(nif: str) -> bool:
"""O nono algarismo é o dígito de controlo (módulo 11) calculado sobre os oito primeiros."""
soma = sum(int(d) * p for d, p in zip(nif[:8], range(9, 1, -1)))
controlo = 11 - soma % 11
return int(nif[8]) == (0 if controlo >= 10 else controlo)
def mascarar(texto: str) -> tuple[str, bool]:
"""Substitui os dados pessoais por [<tipo> oculto]; devolve o texto e True se algo foi ocultado."""
encontrado = False
for tipo, padrao in PADROES.items():
def substituir(m):
nonlocal encontrado
valor = m.group(0)
if tipo == "email" and valor.lower().startswith(GENERICOS):
return valor
if tipo == "nif" and not nif_valido(valor):
return valor
encontrado = True
return f"[{tipo} oculto]"
texto = re.sub(padrao, substituir, texto)
return texto, encontradoCusto estimado
Tokens de entrada por chamada : ≈ 928
| Modelo | Por chamada | Por 1000 chamadas |
|---|---|---|
| Mistral Large · Mistral AI | € 0,00041 | € 0,41 |
| DeepSeek Flash · DeepSeek | € 0,00025 | € 0,25 |
| DeepSeek V4 Pro · DeepSeek | € 0,00108 | € 1,08 |
| GPT-6 Luna · OpenAI | € 0,00008 | € 0,08 |
| GPT-6 Sol · OpenAI | € 0,00164 | € 1,63 |
| Claude Sonnet 5.5 · Anthropic | € 0,00164 | € 1,63 |
| Claude Opus 5.5 · Anthropic | € 0,00327 | € 3,27 |
| Modelo open source alojado por si (Ollama, vLLM) | 0 € em custos de API (apenas o custo do servidor) | |
Apenas o custo de entrada, sem a resposta do modelo. Preços públicos padrão dos fornecedores (Mistral AI, DeepSeek, OpenAI, Anthropic) convertidos de USD para euros à taxa do BCE de 29 de setembro de 2026. Estimativa: 1 token ≈ 3,6 caracteres.
Comum às 3 regiões
Criado pelo autor, sem fonte externa.
Utilização comercial permitida · citar o autor · alterações permitidas.
Apenas texto ou dados: sem acesso a ficheiros, à rede ou a comandos.
Alojado em França (Scaleway, Paris). Sem transferência para fora da UE.
União Europeia · 5/5
Fictícios: nomes, moradas e números inventados, nenhuma pessoa real.
Sem dados de treino: nenhum resumo exigido.
Estados Unidos · 5/5
Fictícios: nomes, moradas e números inventados, nenhuma pessoa real.
Sem dados de treino: nenhuma documentação a publicar.
China · 5/5
Fictícios: nomes, moradas e números inventados, nenhuma pessoa real.
Sem dados de treino; os conteúdos gerados por IA difundidos na China devem ser identificados (2025).
Resumo indicativo em 30/09/2026, sem valor de certificação jurídica. Método e fontes →
Estatísticas
Gostos : 0 · Comentários : 0
Clique num contador para o mostrar ou ocultar, passe o rato sobre o gráfico para ver os detalhes. Uma visualização por visitante por dia, sem robôs; contagem iniciada a 30 de setembro de 2026.
- Identificador
- contextetech--ocultar-dados-pessoais-rgpd
- Tipo
- Salvaguardas
- Ficheiro
- ocultar-dados-pessoais-rgpd.guardrail.json
- Formato
- JSON (application/json)
- Tamanho
- 2.046 bytes (2 KB)
- Codificação
- UTF-8
- Tokens estimados
- ≈ 560
- Idioma
- Português
- Licença
- MIT
- Utilização comercial
- Permitida
- Dados pessoais
- Fictícios (inventados)
- Versão
- 1.0
- Publicado em
- 8 de outubro de 2026 às 19:47
- Atualizado em
- 8 de outubro de 2026 às 19:47
- Armazenamento
- na base de dados
- Utilizações
- 0
- Gostos
- 0
ocultar-dados-pessoais-rgpd.guardrail.json
{
"format": "contextetech/guardrail/v1",
"id": "contextetech/ocultar-dados-pessoais-rgpd",
"description": "Salvaguarda RGPD que oculta NIF (com verificação), telefones +351, IBAN PT e e-mails pessoais nas respostas de um assistente de IA. 12 testes.",
"tags": [
"rgpd",
"dados-pessoais",
"seguranca"
],
"license": "MIT",
"language": "pt",
"type": "pii",
"action": "mask",
"rules": "Ocultar antes de qualquer envio todos os dados que identifiquem ou permitam contactar uma pessoa:\n- endereços de e-mail pessoais (ines.ferreira@…);\n- números de telefone portugueses, com ou sem +351;\n- IBAN portugueses (PT50…);\n- NIF (verifica-se o dígito de controlo).\nDeixar passar: endereços genéricos (geral@, info@, apoio@…), nomes de empresas, montantes, datas, números de encomenda.",
"cases": [
{
"input": "Pode escrever para ines.ferreira@gmail.com para novidades.",
"expected": "block"
},
{
"input": "O meu telemóvel é o 912 345 678.",
"expected": "block"
},
{
"input": "Ligue-me para o +351 21 123 4567 amanhã.",
"expected": "block"
},
{
"input": "Transferência para PT50 0002 0123 1234 5678 9015 4.",
"expected": "block"
},
{
"input": "NIF: 123456789.",
"expected": "block"
},
{
"input": "Fatura a enviar para joao.santos@empresa.pt.",
"expected": "block"
},
{
"input": "Escreva para apoio@contextetech.com para qualquer pergunta.",
"expected": "allow"
},
{
"input": "A sua encomenda n.º 458712 sai amanhã.",
"expected": "allow"
},
{
"input": "O montante é de 1250,00 € com IVA incluído.",
"expected": "allow"
},
{
"input": "Marcação a 12/10/2026 às 14h.",
"expected": "allow"
},
{
"input": "A empresa Construções Silva, Lda. aceitou o orçamento.",
"expected": "allow"
},
{
"input": "Bom dia, em que posso ajudar?",
"expected": "allow"
}
]
}Verificar um texto (Python)
import re
PADROES = {
"email": r"[\w.+-]+@[\w-]+\.[\w.-]+",
"telefone": r"(?:\+351[\s.-]?)?\b(?:9[1236]\d[\s.-]?\d{3}[\s.-]?\d{3}|2\d[\s.-]?\d{3}[\s.-]?\d{4}|2\d{2}[\s.-]?\d{3}[\s.-]?\d{3})\b",
"iban": r"\bPT50(?:\s?\d{4}){5}\s?\d\b",
"nif": r"\b[1235689]\d{8}\b",
}
GENERICOS = ("geral@", "info@", "apoio@", "suporte@", "contacto@")
def nif_valido(nif: str) -> bool:
"""O nono algarismo é o dígito de controlo (módulo 11) calculado sobre os oito primeiros."""
soma = sum(int(d) * p for d, p in zip(nif[:8], range(9, 1, -1)))
controlo = 11 - soma % 11
return int(nif[8]) == (0 if controlo >= 10 else controlo)
def mascarar(texto: str) -> tuple[str, bool]:
"""Substitui os dados pessoais por [<tipo> oculto]; devolve o texto e True se algo foi ocultado."""
encontrado = False
for tipo, padrao in PADROES.items():
def substituir(m):
nonlocal encontrado
valor = m.group(0)
if tipo == "email" and valor.lower().startswith(GENERICOS):
return valor
if tipo == "nif" and not nif_valido(valor):
return valor
encontrado = True
return f"[{tipo} oculto]"
texto = re.sub(padrao, substituir, texto)
return texto, encontrado
Comunidade
Ainda não há comentários. Partilhe a sua experiência, vai ajudar a próxima pessoa.