Vigia

Proteção contra vazamentos e uso indevido da marca

Senha alterada. Entre com a nova senha.

Recuperar senha

Informe o e-mail cadastrado. Se ele estiver vinculado a uma conta, enviaremos um link para você definir uma nova senha.

Definir nova senha

Mínimo 8 caracteres.
Vigia
Proteção contra vazamentos e uso indevido da marca
CategoriaURLUsuárioSenhaForçaExposto emColetado em

Usuários

UsuárioE-mailIdiomaPerfilDomíniosStatusÚltimo login

Domínios monitorados

DomínioColetaCategoriasUnlock auto.Teto/exec.

API

Consulte por API as credenciais expostas dos domínios a que você tem acesso. Cada usuário pode ter uma chave.

Começar

Visão geral e autenticação

Referência

/v1/credentials /v1/domains Erros e boas práticas

How to

Consumir a API com segurança Microsoft Entra ID Google Workspace

Começar

Visão geral

API REST somente leitura, em JSON, sobre HTTPS. Endereço base: https://api-vigia.itool.com.br

Autenticação

Envie a sua chave no cabeçalho Authorization em toda chamada:

Authorization: Bearer vg_SUA_CHAVE

O cabeçalho X-API-Key: vg_SUA_CHAVE também é aceito. A chave dá acesso somente aos domínios do seu usuário (ou ao subconjunto escolhido ao gerá-la). Se um domínio for retirado do seu usuário, ou se a conta for desativada, a chave perde o acesso na hora.

Guias How to

  • Consumir a API com segurança: base para qualquer integração: consulta, filtros e decisão.
  • Microsoft Entra ID: revogar sessões e forçar a troca de senha.
  • Google Workspace: encerrar sessões e forçar a troca de senha com conta de serviço.

Referência

GET /v1/credentials

GEThttps://api-vigia.itool.com.br/v1/credentials

Sem parâmetros, devolve as 10 credenciais descobertas mais recentes de todos os seus domínios, da mais nova para a mais antiga. Use os filtros abaixo para refinar e limit para trazer até 100.

ParâmetroValoresDescrição
limit1 a 100 (padrão 10)Quantidade de credenciais.
domainex.: exemplo.com.brSó um domínio. Precisa estar no escopo da chave.
typeemployees, customers, third_partiesCategoria: funcionários, clientes ou terceiros. Sem o parâmetro, todas.
axiscollected (padrão), exposedData usada para ordenar e filtrar: coleta pelo Vigia ou exposição da credencial.
since2026-10-01 ou 2026-10-01T12:00:00ZA partir desta data (inclusive), no eixo escolhido. UTC.
until2026-10-31 ou 2026-10-31T23:59:59ZAté esta data (inclusive; uma data sem hora vale até o fim do dia). UTC.

Exemplo:

curl -s -H "Authorization: Bearer $VIGIA_API_KEY" \
  "https://api-vigia.itool.com.br/v1/credentials?limit=50&type=employees&since=2026-10-01"

Resposta 200:

{
  "count": 1,
  "limit": 50,
  "axis": "collected",
  "items": [
    {
      "domain": "exemplo.com.br",
      "type": "employees",
      "username": "[email protected]",
      "password": "Exemplo@2024",
      "password_strength": 4,
      "url": "https://portal.exemplo.com.br/login",
      "exposed_at": "2026-10-20T14:03:11.000000Z",
      "collected_at": "2026-10-27T09:00:42.118000Z"
    }
  ]
}
CampoDescrição
domainDomínio monitorado.
typeCategoria: employees, customers ou third_parties.
usernameLogin exposto (mascarado quando a credencial ainda não foi desbloqueada).
passwordSenha exposta. Pode vir null quando ainda não está disponível.
password_strengthForça da senha de 0 a 10 (0–2 crítica, 3–4 fraca, 5–7 média, 8–10 forte).
urlEndereço onde a credencial era usada.
exposed_atData de exposição (ISO 8601, UTC).
collected_atData em que o Vigia descobriu a credencial (ISO 8601, UTC).

Referência

GET /v1/domains

GEThttps://api-vigia.itool.com.br/v1/domains

Lista os domínios que a chave pode consultar: {"domains": ["exemplo.com.br"]}

Referência

Erros

Toda resposta de erro traz {"error": "codigo"}.

HTTPCódigoSignificado
400invalid_limit, invalid_type, invalid_axis, invalid_dateParâmetro inválido.
401invalid_keyChave ausente, errada, revogada, ou usuário inativo.
401key_expiredA chave passou da validade. Gere outra no painel.
403forbidden_domainO domínio pedido não está no escopo da chave.
404 / 405not_found, method_not_allowedRota inexistente ou método diferente de GET.
429—Limite de requisições excedido. Aguarde alguns segundos e tente de novo.
503api_disabledAPI desabilitada no momento.

Boas práticas

  • A resposta contém senhas reais: guarde a chave num cofre de segredos, nunca no código ou em repositório.
  • Se a chave vazar, revogue-a aqui e gere outra. A revogação vale na hora.
  • A coleta acontece uma vez por dia, às 13h00 (horário de Brasília): consultar a cada poucos minutos não traz dados novos. Para buscar só o que é novo sem perder registros, use since sobre collected_at com uma margem de sobreposição (veja Consumir a API com segurança).

How to

Consumir a API com segurança

Base para qualquer integração: consultar sem perder registros, proteger as senhas da resposta e decidir quando agir. Leia antes dos guias de Entra ID e Google Workspace.

Os exemplos de código são ilustrativos. Adapte nomes e exclusões, comece em modo simulação (dry_run) e valide em contas de teste antes de ligar em produção.

1. Visão geral

Um job agendado consulta GET /v1/credentials?type=employees&axis=collected, descarta a senha assim que lê a resposta e procura o funcionário no diretório pelo e-mail. Ele só age quando a conta é única, está ativa, não é de administrador nem de emergência, e a senha dela não foi trocada depois do vazamento. Para agir, força a troca de senha e, em seguida, revoga as sessões.

  1. Consultar a API de forma incremental, sem perder registros.
  2. Filtrar: descartar a senha, ignorar registros mascarados e repetidos.
  3. Casar o e-mail com uma única conta do diretório.
  4. Decidir se a credencial ainda é válida (data do vazamento × última troca de senha).
  5. Agir: forçar a troca de senha e revogar as sessões.
  6. Auditar: registrar só metadados (nunca a senha).

2. Antes de começar

  • Crie um usuário de serviço no Vigia só para a integração. A chave pertence a um usuário e perde o acesso na hora (401 invalid_key) se ele for desativado, perder um domínio ou ficar com troca de senha pendente. Com a chave de uma pessoa, a integração para quando ela sai de férias ou da empresa.
  • Gere a chave com validade de 90 ou 365 dias, limitada aos domínios corporativos que serão remediados. Ela aparece uma única vez: copie direto para um cofre (Azure Key Vault ou Google Secret Manager) e registre a data de expiração no próprio segredo para receber o aviso de vencimento.
  • Faça um inventário: contas de emergência e administradores, contas sincronizadas do Active Directory, domínios federados ou com SSO de terceiros. Eles mudam o que a integração pode e deve fazer.
ItemMicrosoft Entra IDGoogle Workspace
Onde rodarRunbook do Azure Automation (PowerShell 7.4+) ou Azure Functions (plano Flex Consumption)Cloud Run job ou Cloud Functions, acionado pelo Cloud Scheduler
Identidade do jobIdentidade gerenciada atribuída pelo sistema (fora do Azure: app registration com certificado ou credencial federada, nunca segredo de cliente)Conta de serviço com papel de administrador atribuído diretamente, sem delegação em todo o domínio
PermissõesUser.Read.All, User.RevokeSessions.All, User-PasswordProfile.ReadWrite.All (+ IdentityRiskyUser.ReadWrite.All com Entra ID P2)Escopos admin.directory.user e admin.directory.user.security
Papel administrativoUser Administrator (nunca Global Admin nem Privileged Authentication Administrator)Papel personalizado: USERS_RETRIEVE, ORGANIZATION_UNITS_RETRIEVE, USERS_RESET_PASSWORD, USERS_FORCE_PASSWORD_CHANGE, USER_SECURITY_ALL, limitado às UOs de funcionários
Chave do VigiaKey Vault + papel Key Vault Secrets User para a identidadeSecret Manager + secretmanager.secretAccessor só nesse segredo

3. Consultar sem perder registros

  • Use sempre axis=collected. Um vazamento de anos atrás pode ser coletado hoje: o collected_at é recente e o exposed_at é antigo. Uma consulta por exposed_at perderia esses registros.
  • Guarde como marca d'água o maior collected_at já processado (não o relógio do servidor) e consulte com since = marca − 24 h. A sobreposição traz repetições, então deduplique.
  • A API não tem paginação. Se vierem 100 itens, pode haver mais: repita com until igual ao menor collected_at da página até vir menos de 100. Só avance a marca d'água quando a varredura terminar sem erro.
  • Frequência: uma vez por dia, a partir das 13h30 (horário de Brasília). A coleta roda às 13h00; consultar mais que isso não traz nada novo.
  • Erros: repita só em 429 e 5xx, com espera exponencial, variação aleatória (jitter) e limite de tentativas. Em 400, 401 e 403, pare e gere alerta: indicam bug, chave inválida/expirada ou domínio fora do escopo.
Exemplo em Python: consulta incremental com descarte da senha e deduplicação
# EXEMPLO ILUSTRATIVO: adapte e teste antes de usar em produção.
import hmac, hashlib, os, time, random, requests
from datetime import timedelta

API = "https://api-vigia.itool.com.br/v1/credentials"
KEY = os.environ["VIGIA_API_KEY"]             # vem do Key Vault / Secret Manager
PEPPER = os.environ["DEDUP_PEPPER"].encode()  # segredo próprio para o HMAC de deduplicação

def chamar(params, tentativas=6):
    for n in range(tentativas):
        r = requests.get(API, params=params, timeout=30,
                         headers={"Authorization": f"Bearer {KEY}"})
        if r.status_code == 200:
            return r.json()
        if r.status_code in (429, 500, 502, 503, 504):
            time.sleep(random.uniform(0, min(60, 2 ** n)))   # espera exponencial com jitter
            continue
        # 400 / 401 / 403: não repetir. Parar e alertar.
        raise RuntimeError(f"Vigia {r.status_code}: {r.text[:200]}")
    raise RuntimeError("Vigia: tentativas esgotadas")

def coletar(marca_dagua, sobreposicao=timedelta(hours=24)):
    # Lista completa desde a marca d'água. Só avance a marca se nada falhar.
    since = (marca_dagua - sobreposicao).strftime("%Y-%m-%dT%H:%M:%SZ")
    until, vistos, saida = None, set(), []
    while True:
        p = {"type": "employees", "axis": "collected", "limit": 100, "since": since}
        if until:
            p["until"] = until
        pagina = chamar(p)
        for it in pagina["items"]:
            senha = it.pop("password", None) or ""   # a senha sai do registro aqui
            chave = hmac.new(PEPPER, "|".join([it["domain"], it["type"],
                    (it["username"] or "").lower(), it["url"] or "", senha]).encode(),
                    hashlib.sha256).hexdigest()
            senha = None
            if chave not in vistos:
                vistos.add(chave); it["dedup"] = chave; saida.append(it)
        if pagina["count"] < pagina["limit"]:
            return saida
        datas = [i["collected_at"] for i in pagina["items"]]
        if min(datas) == max(datas):
            raise RuntimeError("100+ registros com o mesmo collected_at: consulte por domain")
        until = min(datas)   # inclusivo: as repetições caem na deduplicação

4. Filtrar, casar e decidir

  • Descarte o campo password logo após o parse. A remediação não precisa dele. Nunca grave a resposta bruta em log, chamado ou SIEM. Para deduplicar, use HMAC com uma chave secreta, não um SHA-256 simples da senha.
  • Nunca "teste" a senha vazada tentando fazer login. Não há API para isso, a tentativa pode bloquear a conta e se parece com um ataque de password spray.
  • username mascarado (credencial ainda não desbloqueada no painel) não identifica ninguém: mande para revisão manual.
  • Case o e-mail com uma única conta. Normalize para minúsculas e procure no UPN, mail, proxyAddresses e otherMails (Entra) ou no e-mail principal e aliases (Google). Mais de uma correspondência = revisão manual. Aja sempre pelo ID imutável da conta.
  • Compare a data do vazamento com a última troca de senha. No Entra, use lastPasswordChangeDateTime. No Google não há campo equivalente no usuário: consulte o evento password_edit (aplicação user_accounts) na Reports API. Se a senha foi trocada depois do exposed_at, a credencial provavelmente não vale mais.
SituaçãoDecisão recomendada
Funcionário, e-mail visível, uma conta ativa, fora das exclusões, exposed_at posterior à última troca de senhaAgir: forçar troca e revogar sessões (ou pedir aprovação, se houver)
exposed_at anterior à última troca de senhaAvisar o funcionário. Revogar sessões se houver indício de infostealer
exposed_at no mesmo dia da troca, ou histórico de troca indisponívelRevisão humana
Usuário mascarado, várias contas, convidado ou conta desativadaSó registrar
Conta de emergência, administrador, conta de serviço ou caixa compartilhadaNunca agir automaticamente: escalar para uma pessoa
url de login corporativo (login.microsoftonline.com, accounts.google.com, SSO, VPN)Evidência forte: priorizar
url de site de terceirosRisco de reutilização de senha: avisar e revisar antes de forçar troca

Ordem das ações: primeiro force a troca de senha, depois revogue as sessões. Revogar antes deixa uma janela para o atacante entrar de novo com a senha vazada. Se o vazamento veio de um infostealer (malware que rouba cookies junto com a senha), revogue as sessões mesmo que a senha já tenha sido trocada, e verifique regras de caixa postal, métodos de MFA adicionados e o computador do funcionário.

Proteções: comece em dry_run, limite o número de ações por execução e interrompa o job (com alerta) quando a quantidade de candidatos fugir do normal.

5. Problemas comuns e como evitar

ProblemaCausaComo evitar
Bloqueio indevido em massaVazamentos antigos coletados hoje; senha já trocada; e-mail casado com a conta erradaComparar exposed_at com a última troca; rejeitar correspondências múltiplas; dry_run; limite por execução
Senha vazada em logsO campo password vem em texto claro; Logic Apps, Application Insights e Automation guardam entradas e saídasDescartar o campo no parse; nunca registrar a resposta bruta; log nunca em nível Trace; no Logic Apps, Secure Outputs na ação HTTP e só os campos necessários
Administrador ou conta de emergência bloqueadaPapel alto demais ou sem lista de exclusãoPapel User Administrator (Entra) ou personalizado por UO (Google); excluir por ID; tratar 403 como escalonamento
Troca de senha sem efeitoConta sincronizada do AD, domínio federado, SSO de terceiros ou Password SyncDetectar o tipo de conta; trocar no AD ou no IdP; na nuvem, só revogar sessões
Atacante atende ao pedido de trocaSinalizador de troca sem MFA e sem senha novaforceChangePasswordNextSignInWithMfa ou senha aleatória
Sessões sobrevivem à trocaInfostealers roubam cookies; tokens valem até ~1 hRevogar sessões / signOut depois da troca; revisar regras de e-mail, MFA e OAuth; investigar o computador
Integração para sem avisoChave do Vigia expirada, dono desativado ou com troca de senha pendenteUsuário de serviço no Vigia; chave de 90/365 dias; alerta em 401; aviso de expiração no cofre
429 em cascataVários workers; repetição sem jitterUm worker sequencial por chave; espera com jitter e limite de tentativas
Registros perdidosMais de 100 itens na janela; consulta por exposed_atVarrer para trás com until; marca d'água em collected_at com sobreposição
Aviso ao funcionário parece phishingE-mail com link "redefina aqui"Canal interno conhecido, sem link e sem senha; helpdesk avisado antes

6. LGPD

  • O empregador é o controlador dos dados dos funcionários tratados pela integração. A base usual é o legítimo interesse (art. 7º, IX), com tratamento só do estritamente necessário e transparência (art. 10). Documente o teste de balanceamento.
  • Pela Resolução CD/ANPD nº 15/2024, incidentes de risco relevante com dados de autenticação exigem comunicação à ANPD e aos titulares em até 3 dias úteis, e todo incidente deve ser registrado e guardado por pelo menos 5 anos.
  • Nem todo alerta é um incidente nos sistemas da empresa (o vazamento pode ter sido num site de terceiros ou num computador pessoal), mas registre internamente cada ação tomada, só com metadados. Valide o fluxo com o jurídico.

Referências

  • NIST SP 800-63B-4
  • LGPD (Lei 13.709/2018)
Microsoft Entra ID →

How to

Integrar com Microsoft Entra ID

Revogar sessões e forçar a troca de senha no Microsoft Entra ID quando um funcionário tiver a credencial vazada.

Antes: leia Consumir a API com segurança (consulta, filtros e decisão). Os exemplos de código são ilustrativos: comece em dry_run e valide em contas de teste.

Configuração

  1. Ative a identidade gerenciada atribuída pelo sistema na conta do Azure Automation ou na Function.
  2. Conceda as permissões de aplicativo do Microsoft Graph à identidade. Não há tela no portal para isso: um Privileged Role Administrator faz uma vez, via PowerShell (New-MgServicePrincipalAppRoleAssignment) ou REST (/servicePrincipals/{id}/appRoleAssignments):
    • User.RevokeSessions.All, para revokeSignInSessions;
    • User-PasswordProfile.ReadWrite.All, porque User.ReadWrite.All não cobre o passwordProfile;
    • User.Read.All, para ler accountEnabled, onPremisesSyncEnabled e proxyAddresses.
  3. Atribua ao service principal da identidade o papel User Administrator (New-MgRoleManagementDirectoryRoleAssignment). A documentação da Microsoft exige esse papel para alterar passwordProfile em chamadas só de aplicativo.
  4. Guarde a chave do Vigia no Key Vault e dê à identidade o papel Key Vault Secrets User.
  5. Espere algumas horas (até cerca de 24) antes do primeiro teste: o token da identidade gerenciada fica em cache e as permissões novas demoram a valer.

O que funciona em cada tipo de conta

Tipo de contaComo identificarAção
Somente nuvemonPremisesSyncEnabled diferente de trueForçar troca (PATCH passwordProfile) e depois revokeSignInSessions
Sincronizada do AD (PHS/PTA)onPremisesSyncEnabled = trueRevogar sessões na nuvem e trocar a senha no AD (chamado ou runbook local). A senha da nuvem é sobrescrita pelo AD
Domínio federado (AD FS ou outro IdP)Domínio com autenticação federadaSó revogar sessões; a senha muda no IdP
ConvidadouserType = GuestFora do escopo; só registrar
Locatário com Entra ID P2LicençaPreferir confirmCompromised + política de Acesso Condicional com "Exigir correção de risco", que funciona também em contas híbridas com write-back
  • Use forceChangePasswordNextSignInWithMfa, não só forceChangePasswordNextSignIn. Sem MFA, quem conhece a senha vazada (o atacante) pode atender ao pedido de troca. Alternativa: definir uma senha aleatória que ninguém conhece, com recuperação pelo helpdesk.
  • revokeSignInSessions funciona em qualquer membro do locatário, mas leva alguns minutos. O access token já emitido pode valer até cerca de 1 hora; Exchange, SharePoint e Teams aplicam a revogação quase em tempo real.
  • Contas privilegiadas e de emergência ficam fora do alcance do job. Com User Administrator, a tentativa sobre um administrador falha com 403 Authorization_RequestDenied: trate como "escalar para o SOC" e nunca repita. Exclua as contas de emergência pelo ID do objeto e, se possível, coloque-as numa unidade administrativa de gerenciamento restrito, onde permissões de aplicativo não se aplicam.
  • Limites: o $batch do Graph aceita até 20 requisições; use dependsOn para garantir a ordem. As APIs de riskyUsers aceitam 1 requisição por segundo por locatário: agrupe os usuários numa única chamada de confirmCompromised.
  • Auditoria: no log de auditoria do Entra, a revogação aparece como "Update StsRefreshTokenValidFrom Timestamp" e a troca forçada como "Set force change user password", com o service principal do job como autor.

Valide em laboratório: o PATCH só com os sinalizadores de troca (sem enviar password) e o comportamento do sinalizador em contas sincronizadas do AD.

Exemplo em PowerShell: runbook do Azure Automation com identidade gerenciada
# EXEMPLO ILUSTRATIVO: adapte nomes, exclusões e tratamento de erros. Comece com $dryRun = $true.
Connect-AzAccount -Identity | Out-Null
Connect-MgGraph -Identity -NoWelcome
$key = Get-AzKeyVaultSecret -VaultName 'kv-vigia' -Name 'vigia-api-key' -AsPlainText

$since = (Get-Date).ToUniversalTime().AddHours(-30).ToString('yyyy-MM-ddTHH:mm:ssZ')  # use sua marca d'água
$resp  = Invoke-RestMethod -Headers @{ Authorization = "Bearer $key" } `
         -Uri "https://api-vigia.itool.com.br/v1/credentials?type=employees&axis=collected&limit=100&since=$since"
$itens = $resp.items | Select-Object domain, username, url, exposed_at, collected_at   # nunca a senha
$resp  = $null                                                                        # não escreva a resposta bruta no log

$excluidos = @('<objectId-emergencia-1>', '<objectId-emergencia-2>')
$dryRun = $true; $limite = 10; $acoes = 0
$sel = 'id,userPrincipalName,accountEnabled,userType,onPremisesSyncEnabled,lastPasswordChangeDateTime'

foreach ($i in $itens) {
  if (-not $i.username -or $i.username.Contains('*')) { continue }                 # mascarado: revisão manual
  $e = $i.username.ToLower()
  $f = [uri]::EscapeDataString("userPrincipalName eq '$e' or mail eq '$e' or proxyAddresses/any(p:p eq 'smtp:$e')")
  $u = @((Invoke-MgGraphRequest -Method GET -Uri "v1.0/users?`$filter=$f&`$select=$sel").value)
  if ($u.Count -ne 1) { Write-Output "REVISAR $e ($($u.Count) contas)"; continue }
  $u = $u[0]
  if (-not $u.accountEnabled -or $u.userType -ne 'Member' -or $u.id -in $excluidos) { continue }
  if ([datetime]$i.exposed_at -le [datetime]$u.lastPasswordChangeDateTime) { continue }  # senha já trocada
  if ($u.onPremisesSyncEnabled) { Write-Output "HIBRIDO $($u.id): abrir chamado para troca no AD" }
  if ($dryRun -or $acoes -ge $limite) { Write-Output "SIMULACAO $($u.id)"; continue }

  # 1) forçar troca com MFA (só contas nuvem)  2) revogar sessões, nessa ordem
  $req = @()
  if (-not $u.onPremisesSyncEnabled) {
    $req += @{ id = '1'; method = 'PATCH'; url = "/users/$($u.id)"; headers = @{ 'Content-Type' = 'application/json' }
               body = @{ passwordProfile = @{ forceChangePasswordNextSignIn = $true; forceChangePasswordNextSignInWithMfa = $true } } }
  }
  $rev = @{ id = '2'; method = 'POST'; url = "/users/$($u.id)/revokeSignInSessions" }
  if ($req.Count) { $rev.dependsOn = @('1') }
  $res = Invoke-MgGraphRequest -Method POST -Uri 'v1.0/$batch' -Body (@{ requests = $req + $rev } | ConvertTo-Json -Depth 6)
  $res.responses | ForEach-Object { Write-Output "ACAO $($u.id) req=$($_.id) status=$($_.status)" }  # 403 = escalar
  $acoes++
}

Referências

  • Microsoft Graph: revokeSignInSessions
  • Microsoft Graph: passwordProfile
  • Microsoft Graph: confirmCompromised
  • Conceder permissões do Graph por PowerShell
  • Contas de acesso de emergência no Entra
← Consumir a API com segurançaGoogle Workspace →

How to

Integrar com Google Workspace

Encerrar sessões e forçar a troca de senha no Google Workspace quando um funcionário tiver a credencial vazada.

Antes: leia Consumir a API com segurança (consulta, filtros e decisão). Os exemplos de código são ilustrativos: comece em dry_run e valide em contas de teste.

Configuração

  1. Crie um projeto dedicado no Google Cloud, ative a Admin SDK API e crie uma conta de serviço para o job.
  2. No Admin Console, um super admin cria um papel personalizado com USERS_RETRIEVE, ORGANIZATION_UNITS_RETRIEVE, USERS_RESET_PASSWORD, USERS_FORCE_PASSWORD_CHANGE e USER_SECURITY_ALL.
  3. Em Conta > Funções de administrador > Atribuir contas de serviço, atribua o papel ao e-mail da conta de serviço, limitado às UOs de funcionários. Assim ela chama a Admin SDK em nome próprio, sem delegação em todo o domínio (que o próprio Google recomenda evitar).
  4. Rode o job no Cloud Run com essa conta de serviço como identidade, sem chave JSON. Se rodar no Azure ou na AWS, use Workload Identity Federation, também sem chave.
  5. Guarde a chave do Vigia no Secret Manager, com acesso só para a conta de serviço.

Ações

  • Forçar a troca: users.patch com changePasswordAtNextLogin: true e uma senha nova aleatória enviada em hash (hashFunction). A redefinição pelo administrador encerra as sessões, revoga as senhas de app e pede novo login nos celulares.
  • Derrubar as sessões: depois da troca, POST /admin/directory/v1/users/{userKey}/signOut. Ele não encerra apps de desktop (Gmail, Drive para desktop) nem revoga acessos OAuth de terceiros.
  • Modo agressivo (opcional, para infostealer): revogar tokens OAuth (tokens.delete) e senhas de app (asps.delete). Também derruba integrações legítimas, por isso não deve ser o padrão.
  • Nunca desligue a verificação em duas etapas nem gere códigos de backup no fluxo automático.
  • A senha aleatória deixa o usuário sem acesso até falar com o helpdesk. Usar só changePasswordAtNextLogin é mais brando, mas quem tem a senha vazada é quem escolheria a nova. Deixe isso configurável.

Não aja automaticamente nestes casos

  • Administradores (isAdmin, isDelegatedAdmin): só um super admin redefine a senha deles. Escale para uma pessoa.
  • UOs com SSO de terceiros (Entra, Okta via SAML): changePasswordAtNextLogin não tem efeito. Troque a senha no provedor de identidade; no Google, use só o signOut. Como a API não informa se o usuário é federado, mantenha uma lista configurável dessas UOs.
  • Password Sync com o AD: a senha vazada continua viva no AD. Remedie lá.
  • Contas suspensas ou arquivadas já estão contidas. Um 404 deve ser registrado, não repetido.
  • Limites: 2.400 consultas por minuto por projeto (padrão). O 429 rateLimitExceeded vale para a conta do Workspace inteira: espere 1, 2, 4, 8 e 16 s com jitter.
  • Auditoria: as ações aparecem na Reports API (applicationName=admin) como CHANGE_PASSWORD, CHANGE_PASSWORD_ON_NEXT_LOGIN e RESET_SIGNIN_COOKIES. O Alert Center do Google também tem um alerta nativo de senha vazada: cruze com o Vigia para não agir em dobro.

Valide em uma UO de teste que users.patch e users.signOut funcionam com a conta de serviço e o papel atribuído, antes de liberar para toda a empresa.

Exemplo em Python: remediação com conta de serviço (Cloud Run)
# EXEMPLO ILUSTRATIVO: adapte exclusões e tratamento de erros; valide em uma UO de teste.
import google.auth, secrets, hashlib
from google.auth.transport.requests import AuthorizedSession

SCOPES = ["https://www.googleapis.com/auth/admin.directory.user",
          "https://www.googleapis.com/auth/admin.directory.user.security"]
creds, _ = google.auth.default(scopes=SCOPES)
gs = AuthorizedSession(creds)
DIR = "https://admin.googleapis.com/admin/directory/v1/users"
UOS_EXCLUIDAS = ("/TI/Administradores", "/Federados-SSO", "/Contas-de-servico")

def remediar(email: str, dry_run: bool = True) -> str:
    r = gs.get(f"{DIR}/{email}")                      # aceita e-mail principal ou alias
    if r.status_code == 404:
        return "nao_encontrado"                       # registrar, não repetir
    r.raise_for_status()
    u = r.json()
    if u.get("isAdmin") or u.get("isDelegatedAdmin"):
        return "admin_escalar_para_pessoa"
    if u.get("suspended") or u.get("archived"):
        return "ja_contido"
    if u.get("orgUnitPath", "/").startswith(UOS_EXCLUIDAS):
        return "excluido"
    if dry_run:
        return "simulacao"
    nova = secrets.token_urlsafe(32)                  # ninguém conhece; recuperação pelo helpdesk
    gs.patch(f"{DIR}/{u['id']}", json={
        "password": hashlib.sha1(nova.encode()).hexdigest(),
        "hashFunction": "SHA-1",
        "changePasswordAtNextLogin": True}).raise_for_status()
    nova = None
    gs.post(f"{DIR}/{u['id']}/signOut").raise_for_status()   # depois da troca de senha
    return "remediado"

Referências

  • Google Directory API: users
  • Google Directory API: users.signOut
  • Atribuir papéis de administrador a contas de serviço
  • Limites da Directory API
← Microsoft Entra ID

Get started

Overview and authentication

Reference

/v1/credentials /v1/domains Errors and good practice

How to

Consume the API safely Microsoft Entra ID Google Workspace

Get started

Overview

Read-only REST API, JSON over HTTPS. Base URL: https://api-vigia.itool.com.br

Authentication

Send your key in the Authorization header on every call:

Authorization: Bearer vg_YOUR_KEY

The X-API-Key: vg_YOUR_KEY header is also accepted. The key only reaches your user's domains (or the subset chosen when it was generated). If a domain is removed from your user, or the account is deactivated, the key loses access immediately.

How-to guides

  • Consume the API safely: the basis for any integration: fetching, filtering and deciding.
  • Microsoft Entra ID: revoke sessions and force a password change.
  • Google Workspace: sign out sessions and force a password change with a service account.

Reference

GET /v1/credentials

GEThttps://api-vigia.itool.com.br/v1/credentials

With no parameters, returns the 10 most recently discovered credentials across all your domains, newest first. Use the filters below to narrow it down and limit to get up to 100.

ParameterValuesDescription
limit1 to 100 (default 10)Number of credentials.
domaine.g. example.comA single domain. Must be within the key's scope.
typeemployees, customers, third_partiesCategory. Without it, all categories.
axiscollected (default), exposedDate used to sort and filter: collection by Vigia or credential exposure.
since2026-10-01 or 2026-10-01T12:00:00ZFrom this date (inclusive), on the chosen axis. UTC.
until2026-10-31 or 2026-10-31T23:59:59ZUp to this date (inclusive; a date without time covers the whole day). UTC.

Example:

curl -s -H "Authorization: Bearer $VIGIA_API_KEY" \
  "https://api-vigia.itool.com.br/v1/credentials?limit=50&type=employees&since=2026-10-01"

200 response:

{
  "count": 1,
  "limit": 50,
  "axis": "collected",
  "items": [
    {
      "domain": "example.com",
      "type": "employees",
      "username": "[email protected]",
      "password": "Example@2024",
      "password_strength": 4,
      "url": "https://portal.example.com/login",
      "exposed_at": "2026-10-20T14:03:11.000000Z",
      "collected_at": "2026-10-27T09:00:42.118000Z"
    }
  ]
}
FieldDescription
domainMonitored domain.
typeCategory: employees, customers or third_parties.
usernameExposed login (masked while the credential has not been unlocked).
passwordExposed password. May be null when not yet available.
password_strengthPassword strength from 0 to 10 (0–2 critical, 3–4 weak, 5–7 medium, 8–10 strong).
urlAddress where the credential was used.
exposed_atExposure date (ISO 8601, UTC).
collected_atDate Vigia discovered the credential (ISO 8601, UTC).

Reference

GET /v1/domains

GEThttps://api-vigia.itool.com.br/v1/domains

Lists the domains the key can query: {"domains": ["example.com"]}

Reference

Errors

Every error response carries {"error": "code"}.

HTTPCodeMeaning
400invalid_limit, invalid_type, invalid_axis, invalid_dateInvalid parameter.
401invalid_keyMissing, wrong or revoked key, or inactive user.
401key_expiredThe key is past its expiry date. Generate a new one in the panel.
403forbidden_domainThe requested domain is outside the key's scope.
404 / 405not_found, method_not_allowedUnknown route or a method other than GET.
429—Rate limit exceeded. Wait a few seconds and retry.
503api_disabledThe API is currently disabled.

Good practice

  • Responses contain real passwords: keep the key in a secrets vault, never in code or a repository.
  • If the key leaks, revoke it here and generate a new one. Revocation is immediate.
  • Collection runs once a day, at 1:00 PM Brasília time (UTC−3): polling every few minutes brings no new data. To fetch only what is new without missing records, use since on collected_at with an overlap margin (see Consume the API safely).

How to

Consume the API safely

The basis for any integration: fetch without missing records, protect the passwords in the response and decide when to act. Read it before the Entra ID and Google Workspace guides.

Code samples are illustrative. Adapt names and exclusions, start in simulation mode (dry_run) and validate on test accounts before turning it on in production.

1. Overview

A scheduled job calls GET /v1/credentials?type=employees&axis=collected, drops the password as soon as it reads the response, and looks the employee up in the directory by email. It only acts when the account is unique, active, not an admin or emergency account, and its password was not changed after the leak. To act, it forces a password change and then revokes sessions.

  1. Fetch from the API incrementally, without missing records.
  2. Filter: drop the password, skip masked and duplicate records.
  3. Match the email to exactly one directory account.
  4. Decide whether the credential is still valid (leak date vs. last password change).
  5. Act: force a password change and revoke sessions.
  6. Audit: log metadata only (never the password).

2. Before you start

  • Create a Vigia service user just for the integration. The key belongs to a user and stops working immediately (401 invalid_key) if that user is deactivated, loses a domain or has a pending password change. With a person's key, the integration stops when they go on vacation or leave.
  • Generate the key with a 90- or 365-day expiry, limited to the corporate domains you will remediate. It is shown only once: copy it straight into a vault (Azure Key Vault or Google Secret Manager) and set the expiry date on the secret so you get an expiry warning.
  • Take inventory: emergency and admin accounts, accounts synced from Active Directory, federated domains or third-party SSO. They change what the integration can and should do.
ItemMicrosoft Entra IDGoogle Workspace
Where to runAzure Automation runbook (PowerShell 7.4+) or Azure Functions (Flex Consumption plan)Cloud Run job or Cloud Functions, triggered by Cloud Scheduler
Job identitySystem-assigned managed identity (outside Azure: app registration with a certificate or federated credential, never a client secret)Service account with an admin role assigned directly, no domain-wide delegation
PermissionsUser.Read.All, User.RevokeSessions.All, User-PasswordProfile.ReadWrite.All (+ IdentityRiskyUser.ReadWrite.All with Entra ID P2)Scopes admin.directory.user and admin.directory.user.security
Admin roleUser Administrator (never Global Admin or Privileged Authentication Administrator)Custom role: USERS_RETRIEVE, ORGANIZATION_UNITS_RETRIEVE, USERS_RESET_PASSWORD, USERS_FORCE_PASSWORD_CHANGE, USER_SECURITY_ALL, limited to employee OUs
Vigia keyKey Vault + Key Vault Secrets User role for the identitySecret Manager + secretmanager.secretAccessor on that secret only

3. Fetch without missing records

  • Always use axis=collected. A leak from years ago can be collected today: collected_at is recent and exposed_at is old. Querying by exposed_at would miss those records.
  • Store the highest collected_at you have processed as a watermark (not the server clock) and query with since = watermark − 24 h. The overlap brings repeats, so deduplicate.
  • The API has no pagination. If you get 100 items there may be more: repeat with until set to the page's lowest collected_at until a page has fewer than 100. Only move the watermark once the walk finishes without errors.
  • Frequency: once a day, from 1:30 PM Brasília time (UTC−3). Collection runs at 1:00 PM; polling more often brings nothing new.
  • Errors: retry only on 429 and 5xx, with exponential backoff, random jitter and a retry cap. On 400, 401 and 403, stop and alert: they mean a bug, an invalid/expired key or an out-of-scope domain.
Python sample: incremental fetch that drops the password and deduplicates
# ILLUSTRATIVE SAMPLE: adapt and test before using in production.
import hmac, hashlib, os, time, random, requests
from datetime import timedelta

API = "https://api-vigia.itool.com.br/v1/credentials"
KEY = os.environ["VIGIA_API_KEY"]             # from Key Vault / Secret Manager
PEPPER = os.environ["DEDUP_PEPPER"].encode()  # separate secret for the dedup HMAC

def call(params, attempts=6):
    for n in range(attempts):
        r = requests.get(API, params=params, timeout=30,
                         headers={"Authorization": f"Bearer {KEY}"})
        if r.status_code == 200:
            return r.json()
        if r.status_code in (429, 500, 502, 503, 504):
            time.sleep(random.uniform(0, min(60, 2 ** n)))   # exponential backoff with jitter
            continue
        # 400 / 401 / 403: do not retry. Stop and alert.
        raise RuntimeError(f"Vigia {r.status_code}: {r.text[:200]}")
    raise RuntimeError("Vigia: retries exhausted")

def fetch(watermark, overlap=timedelta(hours=24)):
    # Full list since the watermark. Only move the watermark if nothing failed.
    since = (watermark - overlap).strftime("%Y-%m-%dT%H:%M:%SZ")
    until, seen, out = None, set(), []
    while True:
        p = {"type": "employees", "axis": "collected", "limit": 100, "since": since}
        if until:
            p["until"] = until
        page = call(p)
        for it in page["items"]:
            pwd = it.pop("password", None) or ""     # the password leaves the record here
            key = hmac.new(PEPPER, "|".join([it["domain"], it["type"],
                  (it["username"] or "").lower(), it["url"] or "", pwd]).encode(),
                  hashlib.sha256).hexdigest()
            pwd = None
            if key not in seen:
                seen.add(key); it["dedup"] = key; out.append(it)
        if page["count"] < page["limit"]:
            return out
        dates = [i["collected_at"] for i in page["items"]]
        if min(dates) == max(dates):
            raise RuntimeError("100+ records with the same collected_at: query per domain")
        until = min(dates)   # inclusive: repeats are absorbed by dedup

4. Filter, match and decide

  • Drop the password field right after parsing. Remediation does not need it. Never write the raw response to logs, tickets or a SIEM. To deduplicate, use a keyed HMAC, not a plain SHA-256 of the password.
  • Never "test" the leaked password by trying to sign in. There is no API for it, the attempt can lock the account, and it looks like a password-spray attack.
  • A masked username (credential not yet unlocked in the panel) identifies no one: send it to manual review.
  • Match the email to exactly one account. Lowercase it and search UPN, mail, proxyAddresses and otherMails (Entra) or the primary email and aliases (Google). More than one match = manual review. Always act on the account's immutable ID.
  • Compare the leak date with the last password change. In Entra, use lastPasswordChangeDateTime. Google has no equivalent user field: query the password_edit event (user_accounts application) in the Reports API. If the password was changed after exposed_at, the credential is probably no longer valid.
SituationRecommended decision
Employee, visible email, one active account, not excluded, exposed_at after the last password changeAct: force a change and revoke sessions (or request approval, if you use it)
exposed_at before the last password changeNotify the employee. Revoke sessions if there are signs of an infostealer
exposed_at on the same day as the change, or change history unavailableHuman review
Masked user, several accounts, guest or disabled accountLog only
Emergency account, admin, service account or shared mailboxNever act automatically: escalate to a person
url is a corporate sign-in (login.microsoftonline.com, accounts.google.com, SSO, VPN)Strong evidence: prioritize
url is a third-party sitePassword-reuse risk: notify and review before forcing a change

Order of actions: force the password change first, then revoke sessions. Revoking first leaves a window for the attacker to sign back in with the leaked password. If the leak came from an infostealer (malware that steals cookies along with the password), revoke sessions even if the password was already changed, and check mailbox rules, newly added MFA methods and the employee's computer.

Safeguards: start in dry_run, cap the number of actions per run, and stop the job (with an alert) when the number of candidates is abnormal.

5. Common problems and how to avoid them

ProblemCauseHow to avoid it
Mass lockout by mistakeOld leaks collected today; password already changed; email matched to the wrong accountCompare exposed_at with the last change; reject multiple matches; dry_run; per-run cap
Leaked password in logsThe password field is plaintext; Logic Apps, Application Insights and Automation keep inputs and outputsDrop the field when parsing; never log the raw response; never use Trace log level; in Logic Apps, Secure Outputs on the HTTP action and only the fields you need
Admin or emergency account lockedRole too high or no exclusion listUser Administrator role (Entra) or OU-scoped custom role (Google); exclude by ID; treat 403 as escalation
Password change has no effectAccount synced from AD, federated domain, third-party SSO or Password SyncDetect the account type; change it in AD or at the IdP; in the cloud, only revoke sessions
Attacker completes the change promptChange flag without MFA and without a new passwordforceChangePasswordNextSignInWithMfa or a random password
Sessions survive the changeInfostealers steal cookies; tokens last up to ~1 hRevoke sessions / signOut after the change; review mail rules, MFA and OAuth; investigate the computer
Integration stops silentlyVigia key expired, owner deactivated or with a pending password changeVigia service user; 90/365-day key; alert on 401; expiry warning in the vault
Cascading 429Several workers; retries without jitterOne sequential worker per key; backoff with jitter and a retry cap
Missed recordsMore than 100 items in the window; querying by exposed_atWalk back with until; watermark on collected_at with overlap
Employee notice looks like phishingEmail with a "reset here" linkKnown internal channel, no link and no password; brief the helpdesk first

6. Privacy (LGPD, Brazil)

  • The employer is the controller of the employee data handled by the integration. The usual legal basis is legitimate interest (art. 7, IX), processing only what is strictly necessary, with transparency (art. 10). Document the balancing test.
  • Under ANPD Resolution CD/ANPD 15/2024, relevant-risk incidents involving authentication data must be reported to the ANPD and data subjects within 3 business days, and every incident must be recorded and kept for at least 5 years.
  • Not every alert is an incident in the company's systems (the leak may have happened on a third-party site or a personal computer), but record each action taken internally, with metadata only. Validate the flow with your legal team.

References

  • NIST SP 800-63B-4
  • LGPD (Law 13,709/2018)
Microsoft Entra ID →

How to

Integrate with Microsoft Entra ID

Revoke sessions and force a password change in Microsoft Entra ID when an employee's credential leaks.

First: read Consume the API safely (fetching, filtering and deciding). Code samples are illustrative: start in dry_run and validate on test accounts.

Setup

  1. Enable the system-assigned managed identity on the Azure Automation account or the Function.
  2. Grant the Microsoft Graph application permissions to the identity. There is no portal screen for this: a Privileged Role Administrator does it once, via PowerShell (New-MgServicePrincipalAppRoleAssignment) or REST (/servicePrincipals/{id}/appRoleAssignments):
    • User.RevokeSessions.All, for revokeSignInSessions;
    • User-PasswordProfile.ReadWrite.All, because User.ReadWrite.All does not cover passwordProfile;
    • User.Read.All, to read accountEnabled, onPremisesSyncEnabled and proxyAddresses.
  3. Assign the User Administrator role to the identity's service principal (New-MgRoleManagementDirectoryRoleAssignment). Microsoft's documentation requires this role to change passwordProfile in app-only calls.
  4. Store the Vigia key in Key Vault and give the identity the Key Vault Secrets User role.
  5. Wait a few hours (up to about 24) before the first test: the managed identity's token is cached and new permissions take time to apply.

What works for each account type

Account typeHow to tellAction
Cloud-onlyonPremisesSyncEnabled not trueForce a change (PATCH passwordProfile), then revokeSignInSessions
Synced from AD (PHS/PTA)onPremisesSyncEnabled = trueRevoke sessions in the cloud and change the password in AD (ticket or on-prem runbook). AD overwrites the cloud password
Federated domain (AD FS or another IdP)Domain with federated authenticationRevoke sessions only; the password changes at the IdP
GuestuserType = GuestOut of scope; log only
Tenant with Entra ID P2LicensePrefer confirmCompromised + a Conditional Access policy with "Require risk remediation", which also works for hybrid accounts with writeback
  • Use forceChangePasswordNextSignInWithMfa, not just forceChangePasswordNextSignIn. Without MFA, whoever knows the leaked password (the attacker) can complete the change prompt. Alternative: set a random password nobody knows, with recovery through the helpdesk.
  • revokeSignInSessions works for any tenant member, but takes a few minutes. Access tokens already issued can stay valid for up to about 1 hour; Exchange, SharePoint and Teams enforce the revocation in near real time.
  • Privileged and emergency accounts stay out of the job's reach. With User Administrator, an attempt on an admin fails with 403 Authorization_RequestDenied: treat it as "escalate to the SOC" and never retry. Exclude emergency accounts by object ID and, if possible, put them in a restricted management administrative unit, where application permissions do not apply.
  • Limits: Graph $batch takes up to 20 requests; use dependsOn to enforce order. The riskyUsers APIs accept 1 request per second per tenant: group users into a single confirmCompromised call.
  • Auditing: in the Entra audit log, revocation shows as "Update StsRefreshTokenValidFrom Timestamp" and the forced change as "Set force change user password", with the job's service principal as the actor.

Validate in a lab: a PATCH with only the change flags (without sending password) and how the flag behaves on accounts synced from AD.

PowerShell sample: Azure Automation runbook with managed identity
# ILLUSTRATIVE SAMPLE: adapt names, exclusions and error handling. Start with $dryRun = $true.
Connect-AzAccount -Identity | Out-Null
Connect-MgGraph -Identity -NoWelcome
$key = Get-AzKeyVaultSecret -VaultName 'kv-vigia' -Name 'vigia-api-key' -AsPlainText

$since = (Get-Date).ToUniversalTime().AddHours(-30).ToString('yyyy-MM-ddTHH:mm:ssZ')  # use your watermark
$resp  = Invoke-RestMethod -Headers @{ Authorization = "Bearer $key" } `
         -Uri "https://api-vigia.itool.com.br/v1/credentials?type=employees&axis=collected&limit=100&since=$since"
$items = $resp.items | Select-Object domain, username, url, exposed_at, collected_at   # never the password
$resp  = $null                                                                        # do not write the raw response to logs

$excluded = @('<objectId-emergency-1>', '<objectId-emergency-2>')
$dryRun = $true; $max = 10; $actions = 0
$sel = 'id,userPrincipalName,accountEnabled,userType,onPremisesSyncEnabled,lastPasswordChangeDateTime'

foreach ($i in $items) {
  if (-not $i.username -or $i.username.Contains('*')) { continue }                 # masked: manual review
  $e = $i.username.ToLower()
  $f = [uri]::EscapeDataString("userPrincipalName eq '$e' or mail eq '$e' or proxyAddresses/any(p:p eq 'smtp:$e')")
  $u = @((Invoke-MgGraphRequest -Method GET -Uri "v1.0/users?`$filter=$f&`$select=$sel").value)
  if ($u.Count -ne 1) { Write-Output "REVIEW $e ($($u.Count) accounts)"; continue }
  $u = $u[0]
  if (-not $u.accountEnabled -or $u.userType -ne 'Member' -or $u.id -in $excluded) { continue }
  if ([datetime]$i.exposed_at -le [datetime]$u.lastPasswordChangeDateTime) { continue }  # already changed
  if ($u.onPremisesSyncEnabled) { Write-Output "HYBRID $($u.id): open a ticket to change it in AD" }
  if ($dryRun -or $actions -ge $max) { Write-Output "SIMULATION $($u.id)"; continue }

  # 1) force change with MFA (cloud-only)  2) revoke sessions, in that order
  $req = @()
  if (-not $u.onPremisesSyncEnabled) {
    $req += @{ id = '1'; method = 'PATCH'; url = "/users/$($u.id)"; headers = @{ 'Content-Type' = 'application/json' }
               body = @{ passwordProfile = @{ forceChangePasswordNextSignIn = $true; forceChangePasswordNextSignInWithMfa = $true } } }
  }
  $rev = @{ id = '2'; method = 'POST'; url = "/users/$($u.id)/revokeSignInSessions" }
  if ($req.Count) { $rev.dependsOn = @('1') }
  $res = Invoke-MgGraphRequest -Method POST -Uri 'v1.0/$batch' -Body (@{ requests = $req + $rev } | ConvertTo-Json -Depth 6)
  $res.responses | ForEach-Object { Write-Output "ACTION $($u.id) req=$($_.id) status=$($_.status)" }  # 403 = escalate
  $actions++
}

References

  • Microsoft Graph: revokeSignInSessions
  • Microsoft Graph: passwordProfile
  • Microsoft Graph: confirmCompromised
  • Grant Graph permissions with PowerShell
  • Emergency access accounts in Entra
← Consume the API safelyGoogle Workspace →

How to

Integrate with Google Workspace

Sign out sessions and force a password change in Google Workspace when an employee's credential leaks.

First: read Consume the API safely (fetching, filtering and deciding). Code samples are illustrative: start in dry_run and validate on test accounts.

Setup

  1. Create a dedicated Google Cloud project, enable the Admin SDK API and create a service account for the job.
  2. In the Admin console, a super admin creates a custom role with USERS_RETRIEVE, ORGANIZATION_UNITS_RETRIEVE, USERS_RESET_PASSWORD, USERS_FORCE_PASSWORD_CHANGE and USER_SECURITY_ALL.
  3. Under Account > Admin roles > Assign service accounts, assign the role to the service account's email, limited to employee OUs. It then calls the Admin SDK as itself, without domain-wide delegation (which Google itself recommends avoiding).
  4. Run the job on Cloud Run with that service account as its identity, with no JSON key. If it runs on Azure or AWS, use Workload Identity Federation, also keyless.
  5. Store the Vigia key in Secret Manager, readable only by the service account.

Actions

  • Force a change: users.patch with changePasswordAtNextLogin: true and a new random password sent hashed (hashFunction). An admin reset ends sessions, revokes app passwords and requires signing in again on phones.
  • Sign out sessions: after the change, POST /admin/directory/v1/users/{userKey}/signOut. It does not end desktop apps (Gmail, Drive for desktop) or revoke third-party OAuth access.
  • Aggressive mode (optional, for infostealers): revoke OAuth tokens (tokens.delete) and app passwords (asps.delete). It also breaks legitimate integrations, so it should not be the default.
  • Never turn off 2-Step Verification or generate backup codes in the automated flow.
  • A random password locks the user out until they reach the helpdesk. Using only changePasswordAtNextLogin is milder, but whoever has the leaked password is the one who would choose the new one. Make this configurable.

Do not act automatically in these cases

  • Admins (isAdmin, isDelegatedAdmin): only a super admin can reset their password. Escalate to a person.
  • OUs with third-party SSO (Entra, Okta via SAML): changePasswordAtNextLogin has no effect. Change the password at the identity provider; in Google, use signOut only. Since the API does not say whether a user is federated, keep a configurable list of those OUs.
  • Password Sync with AD: the leaked password is still live in AD. Remediate there.
  • Suspended or archived accounts are already contained. A 404 should be logged, not retried.
  • Limits: 2,400 queries per minute per project (default). 429 rateLimitExceeded applies to the whole Workspace account: wait 1, 2, 4, 8 and 16 s with jitter.
  • Auditing: actions show up in the Reports API (applicationName=admin) as CHANGE_PASSWORD, CHANGE_PASSWORD_ON_NEXT_LOGIN and RESET_SIGNIN_COOKIES. Google's Alert Center also has a native leaked-password alert: cross-check it with Vigia to avoid acting twice.

Validate in a test OU that users.patch and users.signOut work with the service account and its assigned role before rolling out company-wide.

Python sample: remediation with a service account (Cloud Run)
# ILLUSTRATIVE SAMPLE: adapt exclusions and error handling; validate in a test OU.
import google.auth, secrets, hashlib
from google.auth.transport.requests import AuthorizedSession

SCOPES = ["https://www.googleapis.com/auth/admin.directory.user",
          "https://www.googleapis.com/auth/admin.directory.user.security"]
creds, _ = google.auth.default(scopes=SCOPES)
gs = AuthorizedSession(creds)
DIR = "https://admin.googleapis.com/admin/directory/v1/users"
EXCLUDED_OUS = ("/IT/Admins", "/Federated-SSO", "/Service-accounts")

def remediate(email: str, dry_run: bool = True) -> str:
    r = gs.get(f"{DIR}/{email}")                      # accepts primary email or alias
    if r.status_code == 404:
        return "not_found"                            # log, do not retry
    r.raise_for_status()
    u = r.json()
    if u.get("isAdmin") or u.get("isDelegatedAdmin"):
        return "admin_escalate_to_person"
    if u.get("suspended") or u.get("archived"):
        return "already_contained"
    if u.get("orgUnitPath", "/").startswith(EXCLUDED_OUS):
        return "excluded"
    if dry_run:
        return "simulation"
    new = secrets.token_urlsafe(32)                   # nobody knows it; recovery via helpdesk
    gs.patch(f"{DIR}/{u['id']}", json={
        "password": hashlib.sha1(new.encode()).hexdigest(),
        "hashFunction": "SHA-1",
        "changePasswordAtNextLogin": True}).raise_for_status()
    new = None
    gs.post(f"{DIR}/{u['id']}/signOut").raise_for_status()   # after the password change
    return "remediated"

References

  • Google Directory API: users
  • Google Directory API: users.signOut
  • Assign admin roles to service accounts
  • Directory API limits
← Microsoft Entra ID

Novo usuário

Mínimo 8 caracteres.
Recebe o alerta de novas credenciais dos domínios do usuário.

Adicionar domínio

Trocar senha

Este é o seu primeiro acesso. Defina a sua própria senha para continuar.

Mínimo 8 caracteres.

Remover domínio