Hand 80% of your work to AI. Pay for the result 

Arquivo do dia

Um script para o Claude Code que encontra senhas deixadas em texto aberto nos seus arquivos

Slava Sazhin 9 de outubro de 2026 3 min de leitura

Proteja suas contas e seu dinheiro: o Claude Code encontra arquivos com suas senhas em texto aberto, para você tirá-las a tempo.

O arquivo de hoje é um script para o Claude Code. A senha do banco numa anotação, a senha do Wi-Fi na ata de uma reunião, uma tabela de logins no Excel, uma chave de API no arquivo .env de um projeto: quem pega uma cópia dessa pasta pega as contas também, por um notebook perdido, um drive compartilhado ou um backup no lugar errado. O script encontra esses arquivos, para você tirar as senhas deles antes.

Como funciona

O Claude roda o find_secrets.py numa pasta que você indicar. O script lê cada arquivo dela, incluindo documentos do Word (.docx), planilhas do Excel (.xlsx) e arquivos CSV, e mostra o caminho de cada arquivo que tem uma senha, uma chave de acesso ou uma chave privada, junto com o que encontrou ali. Ele nunca mostra a senha em si, então os valores não vão parar no chat, e não muda nada. Uma senha conta quando a palavra “senha” está ao lado dela, em inglês, russo, português ou sérvio (password, пароль, senha, lozinka); uma coluna chamada “Senha” numa planilha também basta. Uma senha sem essa palavra por perto ele não encontra. As chaves ele reconhece pelo formato: chaves privadas, chaves da AWS e do GitLab, tokens JWT, arquivos de conta de serviço do Google. Ele pula as pastas .git, node_modules e venv, e de um arquivo grande lê só os primeiros 2 MB.

No nosso teste ele apontou todos os 21 arquivos que plantamos: anotações com senhas nos quatro idiomas, uma tabela de senhas no Word e no Excel, uma exportação do navegador em CSV, um arquivo .env, uma chave privada, tokens da AWS, do GitLab e JWT, uma string de conexão de banco de dados. Exemplos de preenchimento como changeme, ${DB_PASSWORD} e os.environ e uma frase como “nunca compartilhe a sua senha” ele deixou passar. Depois pedimos ao Claude, nos quatro idiomas, que o rodasse numa pasta Documents de teste e mostrasse o que encontrou. Em 6 execuções de 6 o Claude listou os arquivos e o que havia neles, não abriu nenhum e se ofereceu para olhar dentro só se você pedir.

O que mudar

Não mudamos a lógica. Dois caracteres de travessão dentro de um padrão de busca viraram os seus códigos, para que o arquivo em inglês seja ASCII puro; ele encontra exatamente o mesmo. Rode o script na pasta com os seus documentos, não na pasta pessoal inteira: uma pasta grande leva minutos, até um segundo para cada arquivo grande, e a pasta pessoal também tem arquivos onde uma chave deve ficar, como os de ~/.ssh. Para pegar uma senha ao lado de uma palavra em outro idioma, digamos Passwort, peça ao Claude para acrescentá-la à linha LABELS do script. Quando ele apontar um arquivo, abra o arquivo você mesmo, passe a senha para um gerenciador de senhas e troque-a se a pasta já foi compartilhada. Se você pedir ao Claude para abrir o arquivo, a senha vai para o chat.

Como instalar

Os pedidos exatos estão na nota abaixo. O primeiro coloca o script em ~/.claude/scripts e verifica se o python3 está no computador. Essa pasta guarda as configurações do próprio Claude, então ele pergunta antes de gravar ali: responda Yes. O segundo roda o script na sua pasta Documents. A verificação da nota: uma pasta de teste com um note.txt que tem a linha “Senha do e-mail: Kofe2026sad”. O script aponta o note.txt e a palavra “senha”, e não mostra Kofe2026sad.

find_secrets.py 5 KB Baixar
#!/usr/bin/env python3
"""Encontra numa pasta os arquivos em que uma senha ou uma chave de acesso está escrita como texto aberto.

Uso: python3 find_secrets.py <pasta>
Mostra o caminho do arquivo e o tipo do que encontrou. Nunca mostra as senhas em si.
Não muda nada, só lê. Também lê documentos do Word e planilhas do Excel.
"""
import html
import os
import re
import sys
import zipfile

# Quanto ler de um arquivo: de um arquivo grande só se lê o começo.
READ_MAX = 2_000_000
# Pastas em que a verificação não entra.
SKIP_DIRS = {'.git', 'node_modules', '__pycache__', '.venv', 'venv'}
# A palavra “senha” em inglês, russo, português e montenegrino.
LABELS = r'password|passwd|pwd|db_pass|\u043f\u0430\u0440\u043e\u043b|senha|lozink'

# Sinais fortes: só uma chave de verdade tem um texto assim.
STRONG = [
    ('chave privada', re.compile(r'-----BEGIN [A-Z ]*PRIVATE KEY-----')),
    ('token JWT', re.compile(r'\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.')),
    ('chave de conta de serviço do Google', re.compile(r'"type"\s*:\s*"service_account"')),
    ('token do GitLab', re.compile(r'glpat-[A-Za-z0-9_-]{15,}')),
    ('chave da AWS', re.compile(r'\bAKIA[0-9A-Z]{16}\b')),
    ('a palavra “senha” sozinha numa linha ou célula', re.compile(r'(?im)^[ \t"]*(?:' + LABELS + r')\w{0,30}(?:[ \t]+\w{1,30}){0,2}[ \t"\r]*$')),
]
# Sinais fracos: uma palavra como “senha”, secret ou api_key e o valor depois dela.
SOFT = [
    ('senha', re.compile(r'(?i)\b(?:' + LABELS + r')[\w \t"\'-]{0,60}[=:]\s*(["\']?)(\S{6,})')),
    ('senha', re.compile(r'(?i)\b(?:' + LABELS + r')\w{0,30}(?:[ \t]+\S{1,30}){0,6}?'
                          r'(?:[ \t]*[-\u2013\u2014:=][ \t]*|[ \t]+)(["\']?)((?=\S{0,200}?[\d!@#$%^&*?_+])\S{6,200})')),
    ('segredo', re.compile(r'(?i)\b[A-Z0-9_]*secret[A-Z0-9_]*["\']?\s*[=:]\s*(["\']?)(\S{8,})')),
    ('chave de acesso', re.compile(r'(?i)\b[A-Z0-9_]*(?:api[_-]?key|apikey|access[_-]?key|auth[_-]?'
                                r'token|bot[_-]?token)[A-Z0-9_]*["\']?\s*[=:]\s*(["\']?)(\S{8,})')),
    ('senha numa string de conexão', re.compile(r'(?i)(?:Password|Pwd)\s*=\s*([^;\s]{4,});')),
]
# O valor é uma referência a uma variável ou configuração, não a senha em si.
REFERENCE = re.compile(r"""(?ix)^(
   os\.environ|os\.getenv|environ|getenv|process\.env|System\.|Environment\.|configuration|
   config\.|self\.|this\.|cfg\.|settings\.|require\(|import\b|input\(|prompt
)""")
BRACKET = re.compile(r'^(\$\{?[A-Za-z_]|%\(|\{\{|\{[A-Za-z_]|<[A-Za-z_]|@[A-Za-z_]|\[)')
# O valor é um exemplo de preenchimento, não uma senha. É comparado por inteiro.
DUMMY = re.compile(r'(?ix)^(None|null|nil|true|false|undefined|x{3,}|y{3,}|\*+|\.{3,}|'
                   r'change_?me|your_?\w*|test|password|secret|dummy|example|sample|'
                   r'placeholder|\d{1,4}|["\'`]*)$')


def value(m):
    g = [x for x in m.groups() if x is not None]
    return (g[-1] if g else '').strip('"\'`,;)')


def is_secret(v):
    if not v or len(v) < 6:
        return False
    if REFERENCE.match(v) or BRACKET.match(v):
        return False
    if DUMMY.fullmatch(v):
        return False
    return True


def read_text(path):
    """Texto do arquivo. Para um documento do Word ou uma planilha do Excel, o texto das suas partes, um parágrafo ou célula por linha."""
    low = path.lower()
    if low.endswith(('.docx', '.xlsx')):
        with zipfile.ZipFile(path) as z:
            parts = [z.open(n).read(READ_MAX) for n in z.namelist() if n.endswith('.xml')]
        text = b'\n'.join(parts).decode('utf-8', 'ignore')
        text = re.sub(r'</(?:w:p|w:tc|si|c|row)>', '\n', text)
        return html.unescape(re.sub(r'<[^>]+>', '', text))
    with open(path, 'rb') as fh:
        text = fh.read(READ_MAX).decode('utf-8', 'ignore')
    # Numa tabela CSV, cada campo vai para uma linha própria.
    return re.sub(r'[,;\t]', '\n', text) if low.endswith(('.csv', '.tsv')) else text


def signs(path):
    """Tipos do que foi encontrado no arquivo. Os valores em si nunca são devolvidos."""
    try:
        data = read_text(path)
    except Exception:
        return []
    found = [name for name, rx in STRONG if rx.search(data)]
    for name, rx in SOFT:
        for m in rx.finditer(data):
            if is_secret(value(m)):
                found.append(name)
                break
    return sorted(set(found))


def main():
    sys.stdout.reconfigure(encoding='utf-8', errors='replace')
    root = sys.argv[1] if len(sys.argv) > 1 else '.'
    if not os.path.isdir(root):
        sys.exit(f'Pasta não encontrada: {root}')
    total = 0
    for folder, dirs, files in os.walk(root):
        dirs[:] = [d for d in dirs if d not in SKIP_DIRS]
        for f in files:
            path = os.path.join(folder, f)
            if os.path.islink(path) or not os.path.isfile(path):
                continue
            found = signs(path)
            if found:
                total += 1
                print(f'{path}: {", ".join(found)}')
    print(f'Arquivos com senha ou chave em texto aberto: {total}')


if __name__ == '__main__':
    main()
como-instalar.md 2 KB Baixar
# Como encontrar senhas que estão nos seus arquivos como texto aberto

1. Peça ao Claude: “Coloque o arquivo baixado find_secrets.py na pasta ~/.claude/scripts e verifique se o python3 está no computador; se não estiver, instale”.
2. Peça ao Claude: “Rode ~/.claude/scripts/find_secrets.py na pasta $HOME/Documents e me mostre o que ele encontrou”. Em vez de Documents, indique a pasta onde ficam os seus arquivos de trabalho.
3. O script mostra o caminho de cada arquivo encontrado e o que há nele: uma senha, uma chave de acesso ou uma chave privada. Ele procura a senha ao lado da palavra “senha” ou password, e uma senha sem essa palavra ao lado ele não encontra. Ele também lê documentos do Word, planilhas do Excel e arquivos CSV. Ele não mostra as senhas em si e não muda nada nos arquivos.

Verificação:

1. Peça ao Claude: “Crie a pasta $HOME/secrets-test, coloque nela um arquivo note.txt com a linha Senha do e-mail: Kofe2026sad e rode ~/.claude/scripts/find_secrets.py nessa pasta”. O script mostra o caminho do arquivo note.txt e a palavra “senha”, e não mostra a senha em si.
2. Peça ao Claude: “Apague a pasta $HOME/secrets-test”.

Este artigo valeu o seu tempo?

Uma masterclass nova toda semana

Um praticante mostra em vídeo uma configuração que funciona e entrega todos os arquivos dela. $11 por mês.

Gravação mais recente · 30 de setembro de 2026 Claude apologised and promised never to do it again? Do not trust promises, set up hooks Slava Sazhin

Fale com a equipe do Combobulating