Se a chave de API da CaptchaAI está em um .env versionado por engano ou colada no canal do time, o conserto não é trocar a chave: é tirar o segredo do código. Com o HashiCorp Vault ela fica cifrada em um caminho KV, cada worker a lê em tempo de execução com identidade própria e toda leitura entra na trilha de auditoria. Nenhuma linha do seu código de resolução precisa saber qual é a chave.
São quatro passos:
- Gravar a chave em um caminho KV v2
- Escrever uma política de leitura restrita aos workers
- Ler o segredo em tempo de execução no Python
- Repetir o padrão em Node.js, sem SDK
Antes de começar
- Servidor HashiCorp Vault (auto-hospedado ou HCP Vault)
- Acesso ao Vault por CLI ou API
- Uma chave de API da CaptchaAI ativa (qualquer plano serve — o BASIC custa US$ 15/mês e inclui 5 threads)
- Python 3.8+ ou Node.js 18+
Um servidor em modo dev já basta para percorrer o guia; a sequência em produção é a mesma.
O que o Vault resolve no fluxo de resolução de CAPTCHA
| Sem Vault | Com Vault |
|---|---|
Chave de API no arquivo .env ou no código |
Chave cifrada em um caminho KV do Vault |
| Chave compartilhada por Slack ou e-mail | Leitura por API autenticada |
| Nenhuma trilha de auditoria | Cada leitura registrada com identidade |
| Rotação manual da chave | Rotação sem novo deploy |
| Mesma chave em todos os ambientes | Uma chave por ambiente, com políticas distintas |
Há também conformidade: quando o pipeline processa dados pessoais, a LGPD (RGPD em Portugal) espera controle de acesso demonstrável. "Quem leu a credencial de produção em 12 de março?" o Vault responde com um registro; o .env, não.
Passo 1: guarde a chave no caminho KV
O motor KV v2 mantém versões do segredo, o que facilita voltar atrás se uma rotação der errado.
# Enable the KV secrets engine (if not already enabled)
vault secrets enable -path=secret kv-v2
# Store the CaptchaAI API key
vault kv put secret/captchaai api_key="YOUR_API_KEY"
# Verify
vault kv get secret/captchaai
Passo 2: escreva a política de leitura
Os workers de resolução não precisam gravar nada. Dê a eles apenas leitura, no caminho exato — nada de curingas:
# captcha-worker-policy.hcl
path "secret/data/captchaai" {
capabilities = ["read"]
}
path "secret/metadata/captchaai" {
capabilities = ["read"]
}
Aplique a política:
vault policy write captcha-worker captcha-worker-policy.hcl
No KV v2 o dado vive em secret/data/... e os metadados em secret/metadata/.... Esquecer o segundo caminho é a causa mais comum de 403 Forbidden inexplicável.
Passo 3: leia o segredo em Python
O cliente hvac busca a chave uma vez, mantém em memória e renova de hora em hora. Assim o Vault não vira ponto de falha a cada resolução: se cair por alguns minutos, o worker segue com a chave em cache.
# vault_solver.py
import os
import time
import hvac
import requests
# Connect to Vault
vault_client = hvac.Client(
url=os.environ.get("VAULT_ADDR", "http://127.0.0.1:8200"),
token=os.environ.get("VAULT_TOKEN"),
)
def get_api_key():
"""Retrieve CaptchaAI API key from Vault."""
secret = vault_client.secrets.kv.v2.read_secret_version(
path="captchaai",
mount_point="secret",
)
return secret["data"]["data"]["api_key"]
class CaptchaSolver:
"""CAPTCHA solver with Vault-managed credentials."""
def __init__(self):
self.api_key = get_api_key()
self.session = requests.Session()
self._key_fetched_at = time.time()
self._key_refresh_interval = 3600 # Re-fetch key hourly
def _refresh_key_if_needed(self):
"""Periodically refresh the key from Vault."""
if time.time() - self._key_fetched_at > self._key_refresh_interval:
self.api_key = get_api_key()
self._key_fetched_at = time.time()
def solve(self, sitekey, pageurl):
"""Solve reCAPTCHA v2 using Vault-managed key."""
self._refresh_key_if_needed()
# Submit
resp = self.session.get("https://ocr.captchaai.com/in.php", params={
"key": self.api_key,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": pageurl,
"json": "1",
})
result = resp.json()
if result.get("status") != 1:
raise Exception(f"Submit failed: {result.get('request')}")
task_id = result["request"]
time.sleep(15)
for _ in range(25):
poll = self.session.get("https://ocr.captchaai.com/res.php", params={
"key": self.api_key,
"action": "get",
"id": task_id,
"json": "1",
})
poll_result = poll.json()
if poll_result.get("status") == 1:
return poll_result["request"]
if poll_result.get("request") != "CAPCHA_NOT_READY":
raise Exception(f"Error: {poll_result.get('request')}")
time.sleep(5)
raise Exception("Timeout")
# Usage
solver = CaptchaSolver()
token = solver.solve(
"6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
"https://www.google.com/recaptcha/api2/demo"
)
print(f"Token: {token[:30]}...")
Passo 4: o mesmo padrão em Node.js
A API HTTP do Vault dispensa SDK: um GET no caminho data com o cabeçalho X-Vault-Token devolve o segredo. O ciclo é idêntico ao do Python — in.php envia a tarefa, res.php consulta até o token chegar.
// vault_solver.js
const axios = require('axios');
const VAULT_ADDR = process.env.VAULT_ADDR || 'http://127.0.0.1:8200';
const VAULT_TOKEN = process.env.VAULT_TOKEN;
async function getApiKey() {
const resp = await axios.get(
`${VAULT_ADDR}/v1/secret/data/captchaai`,
{ headers: { 'X-Vault-Token': VAULT_TOKEN } }
);
return resp.data.data.data.api_key;
}
class CaptchaSolver {
constructor() {
this.apiKey = null;
this.keyFetchedAt = 0;
this.refreshInterval = 3600000; // 1 hour
}
async init() {
this.apiKey = await getApiKey();
this.keyFetchedAt = Date.now();
}
async refreshKeyIfNeeded() {
if (Date.now() - this.keyFetchedAt > this.refreshInterval) {
this.apiKey = await getApiKey();
this.keyFetchedAt = Date.now();
}
}
async solve(sitekey, pageurl) {
await this.refreshKeyIfNeeded();
const submit = await axios.get('https://ocr.captchaai.com/in.php', {
params: {
key: this.apiKey, method: 'userrecaptcha',
googlekey: sitekey, pageurl, json: '1',
},
});
if (submit.data.status !== 1) throw new Error(submit.data.request);
const taskId = submit.data.request;
await new Promise(r => setTimeout(r, 15000));
for (let i = 0; i < 25; i++) {
const poll = await axios.get('https://ocr.captchaai.com/res.php', {
params: { key: this.apiKey, action: 'get', id: taskId, json: '1' },
});
if (poll.data.status === 1) return poll.data.request;
if (poll.data.request !== 'CAPCHA_NOT_READY') throw new Error(poll.data.request);
await new Promise(r => setTimeout(r, 5000));
}
throw new Error('Timeout');
}
}
(async () => {
const solver = new CaptchaSolver();
await solver.init();
const token = await solver.solve(
'6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-',
'https://www.google.com/recaptcha/api2/demo'
);
console.log(`Token: ${token.slice(0, 30)}...`);
})();
Rotação da chave sem novo deploy
- Gere uma nova chave de API no painel da CaptchaAI
- Atualize o Vault:
vault kv put secret/captchaai api_key="NEW_KEY" - Os workers pegam a nova chave no próximo ciclo de atualização (uma hora, no exemplo acima)
- Revogue a chave antiga no painel depois que todos os workers tiverem atualizado
Nenhuma alteração de código, nenhum deploy. Para encurtar a janela em que as duas chaves convivem, reduza _key_refresh_interval: 300 s é confortável para quem gira credenciais com frequência.
Como autenticar cada tipo de worker
Token estático resolve no notebook; em produção ele vira dívida. Duas perguntas guiam a escolha:
- O worker roda em plataforma que já emite identidade (Kubernetes, EC2, Lambda)? Use essa identidade.
- Alguém copia o segredo à mão em algum momento? Então ainda há um ponto manual a eliminar.
| Método | Indicado para | Configuração |
|---|---|---|
| Token | Desenvolvimento, CI/CD | Variável de ambiente VAULT_TOKEN |
| AppRole | Serviços de produção | role ID + secret ID |
| Kubernetes | Cargas em K8s | JWT da service account |
| AWS IAM | Workers em EC2/Lambda | Role da instância |
AppRole: o padrão recomendado em produção
Um cenário concreto: um time de QA roda os workers em sa-east-1 (São Paulo) para reduzir o RTT e mantém o Vault em outra conta. Com AppRole, o role ID vai na imagem do container e o secret ID é entregue pelo orquestrador com TTL curto — nenhum token de longa duração cruza a rede.
# AppRole authentication — no static token needed
vault_client = hvac.Client(url=os.environ["VAULT_ADDR"])
vault_client.auth.approle.login(
role_id=os.environ["VAULT_ROLE_ID"],
secret_id=os.environ["VAULT_SECRET_ID"],
)
# Now read the secret
secret = vault_client.secrets.kv.v2.read_secret_version(path="captchaai")
api_key = secret["data"]["data"]["api_key"]
Quando algo dá errado
| Problema | Causa provável | Correção |
|---|---|---|
403 Forbidden do Vault |
A política não cobre o caminho lido | Confira secret/data/... e secret/metadata/... em captcha-worker-policy.hcl |
VAULT_TOKEN expirado |
TTL do token estourou | Migre para AppRole, com renovação automática |
| A chave não atualiza | Intervalo de atualização longo demais | Reduza _key_refresh_interval |
| Vault indisponível | Rede ou servidor fora do ar | Mantenha a chave em cache na memória como plano B |
ERROR_WRONG_USER_KEY da CaptchaAI |
O valor gravado no Vault veio com espaço ou quebra de linha | Regrave com vault kv put e confira com vault kv get |
Perguntas frequentes
Vale a pena usar o Vault se eu tenho só um worker?
Vale mais pela rotação do que pela criptografia: mesmo com um worker só, você troca a chave sem republicar o serviço. Em script pessoal, uma variável de ambiente resolve — veja como proteger credenciais em variáveis de ambiente.
Cada leitura do Vault atrasa a resolução do CAPTCHA?
Não, porque a leitura não acontece a cada resolução. O cliente guarda a chave em memória até o intervalo vencer: uma requisição por hora e por processo, irrelevante diante do tempo de resolução.
Como isolo produção de staging?
Use caminhos separados — secret/captchaai/dev, secret/captchaai/staging, secret/captchaai/prod — e uma política por ambiente. Use também chaves de API distintas: isso separa o consumo de threads.
Dá para trocar o Vault pelo AWS Secrets Manager?
Dá, e o desenho não muda: leitura em tempo de execução com boto3, cache em memória e rotação sem deploy. Você perde a política declarativa em HCL e ganha uma peça a menos para operar.
O Vault muda alguma coisa nos tipos de CAPTCHA suportados?
Nada — ele cuida só da credencial. Os tipos seguem os mesmos: reCAPTCHA v2 e v3 (incluindo Enterprise), Cloudflare Turnstile e Cloudflare Challenge, GeeTest v3, CAPTCHAs de imagem/OCR e de grade, além de CaptchaFox (beta), Friendly Captcha (beta) e Lemin (beta). hCaptcha e FunCaptcha não são suportados; o GeeTest v4 está anunciado como "em breve".
Artigos relacionados
- Restringir a chave de API por lista de IPs
- Rotação da chave de API da CaptchaAI
- Integração com o Google Cloud Functions
Próximas etapas
Comece pequeno: mova uma chave de staging para o Vault, valide a leitura pelo worker e só depois toque em produção. Crie sua chave de API e faça a primeira leitura hoje.
Guias relacionados: