04 · DATABASE MANAGER

Acesso à produção com pedido, aprovação de outra pessoa e prazo.

A sua organização define, banco a banco, quem aprova, em que horário uma mudança entra e por quanto tempo um acesso vale. A plataforma faz essa regra valer: a senha nasce no Agente, o dado sensível volta mascarado e o guardrail recusa o DELETE sem WHERE.

Abre uma conversa no WhatsApp. Prefere e-mail? heimdall@yops.cloud

O que faz

O pedido

Quem precisa de acesso informa no pedido o banco, as operações — SELECT, INSERT, UPDATE, DELETE —, as tabelas ou o schema e o motivo, que é obrigatório. Se o pedido mistura leitura e escrita, ele é tratado inteiro como escrita.

Aprovadores nomeados banco a banco

Cada banco tem três listas de aprovadores: acesso, comando e migration. Se uma dessas listas está vazia, o banco recusa todo pedido daquele tipo com NO_NAMED_APPROVERS. O banco pode exigir duas aprovações. Ninguém aprova o próprio pedido, seja qual for a sua permissão, e o papel de quem aprova é consultado na organização na hora, e não lido do token.

Por quanto tempo vale

A sua organização define, para cada banco, um prazo padrão e um teto, separados para leitura e para escrita — por exemplo, escrita por 2 horas, com teto de 8. A contagem começa na aprovação. Independentemente da configuração, nenhum acesso passa de 30 dias, e um pedido que ninguém decide expira em 7.

Uma senha que ninguém vê

Com o provisionamento ligado, a aprovação cria no PostgreSQL uma role exclusiva daquele acesso, com apenas o que foi pedido e com VALID UNTIL no fim do prazo. A senha nasce no Agente e não sai de lá; quem conecta passa pelo proxy, e o Agente autentica no lugar da pessoa.

O acesso termina em três camadas

O próprio PostgreSQL recusa a role no fim do prazo, mesmo com o Heimdall inteiro fora do ar. O proxy pergunta à API, a cada conexão nova, se o acesso ainda vale — e, numa sessão aberta, o primeiro comando depois de uma revogação é recusado. Por fim, uma varredura remove a role por meio do Agente.

Dentro da sessão

Cada resultado volta com as colunas sensíveis mascaradas, e cada statement passa pelos guardrails da sua organização antes de chegar ao banco. As duas seções seguintes mostram como: masking e guardrail.

Mudança de schema

Uma migration passa por submissão, análise e aprovação de outra pessoa, e é agendada dentro da janela de horário do banco, num fuso IANA. Quem a executa é o Agente, dentro da sua rede. A janela é conferida na aprovação, no agendamento e no instante de rodar.

Na emergência: o break-glass

Quem está de plantão entra sem esperar aprovação, por até 4 horas. É uma permissão própria, que nenhum papel tem por padrão. Os aprovadores são avisados antes, e, se o aviso falhar, a entrada é recusada. O masking e os guardrails continuam valendo, exceto o guardrail que a sua organização marcou como contornável, que passa a avisar em vez de recusar. Depois, outra pessoa revisa o evento.

O que fica registrado

O Heimdall registra cada pedido, aprovação, recusa, revogação e break-glass, além do texto de cada comando SQL executado pelo proxy, com o tipo, o desfecho, as linhas afetadas e a duração. Registra também cada classificação de coluna e cada regra de masking criada ou apagada.

Como funciona

Um acesso, do pedido ao fim do prazo:

  1. A pessoa faz o pedido pelo CLI. Os aprovadores nomeados daquele banco são avisados.
  2. Outra pessoa aprova. A API confere a lista, o papel e o prazo, e grava a decisão.
  3. A API coloca o trabalho na fila. O Agente vinculado ao environment daquele banco o retira da fila e cria a role, com a credencial administrativa que só ele tem.
  4. A pessoa conecta pela borda, com o certificado pessoal dela. O proxy pergunta à API se o acesso vale e autentica no banco com a senha que nunca saiu do Agente.
  5. A cada statement, o proxy confere os guardrails antes de mandá-lo ao banco. A cada resultado, troca o valor das colunas mascaradas antes de devolvê-lo. Nada muda no banco, e o resultado não vai para a API.
  6. O que um guardrail recusou vira um comando: se outra pessoa aprovar, ele passa uma vez, na sessão de quem pediu.
  7. No fim do prazo, o PostgreSQL recusa a role, e o Agente a remove.
TERMINALheimdall db access
heimdall db access request pg-orders-orders --operations SELECT,UPDATE --tables public.orders,public.customers --reason "corrigir pedidos com total errado" --task-key OPS-101
✓ Access request submitted for database 'pg-orders-orders'
  Operations: SELECT, UPDATE
  Reason: corrigir pedidos com total errado
  Task: OPS-101
  Expires: 2026-10-10T08:13:58.978413Z
  Duration: this database's default for those operations
  ('heimdall db expiry show --database pg-orders-orders' shows the defaults and the ceilings.)
  Status: pending approval
  An approver decides it with 'heimdall db access approve <grant-id>'.

heimdall db access approve 05512517-3276-4918-af87-79ef234fbd9d
✓ Access request 05512517-3276-4918-af87-79ef234fbd9d approved
  Status: active
  The requester can connect now with 'heimdall db connect <database>'.

NO BANCO: A ROLE, COM VALID UNTIL, E SÓ O QUE FOI PEDIDO

psql -h 127.0.0.1 -U orders_admin -d orders -tA -c "select rolname, rolvaliduntil from pg_roles where rolname like 'hd\_%'" -c "select table_name, privilege_type from information_schema.role_table_grants where grantee like 'hd\_%' order by 1, 2"
hd_pg_orders_orders_7cyjmwi7_hdm|2026-10-10 08:14:06+00
customers|SELECT
customers|UPDATE
orders|SELECT
orders|UPDATE
Saída real de heimdall db access request e heimdall db access approve, e a role criada no banco.

masking pela coluna de origem

Quem consulta a produção pelo proxy recebe [CPF] no lugar do CPF. A regra segue a coluna de onde o valor veio, e não o nome com que ele chega.

Primeiro classificar, depois mascarar

Classificar uma coluna é registrar o que ela guarda — dado pessoal, financeiro, de saúde, credencial — num vocabulário fechado, que a sua organização pode estender. Mascarar é outra coisa: é criar a regra que troca o valor. Cada regra indica uma coluna, uma estratégia e um escopo — a organização inteira, uma instância, um banco, um schema ou uma tabela — e só vale dentro desse escopo.

Pela coluna de origem

Para cada campo do resultado, o PostgreSQL informa de que tabela e de que coluna ele veio, e o proxy aplica a regra dessa coluna. Renomear com AS ou passar por subquery, CTE ou view não muda a origem. Um campo que não vem de coluna nenhuma — expressão, função, UNION, a linha inteira — sai com a estratégia mais restritiva em vigor na sessão, mesmo um count(*): como não sabe o que uma função revela, o proxy não deixa que ela revele nada.

Os outros caminhos

Numa sessão mascarada, COPY … TO STDOUT é recusado, e a mensagem orienta a ler as linhas com SELECT. A mensagem de erro do servidor chega só com o código, porque o texto dela pode citar o valor. Copiar a coluna para uma tabela temporária não adianta: a leitura volta mascarada. E a decisão não depende da ferramenta: o proxy pede ao banco a descrição de cada resultado antes de executar a consulta, e uma linha sem decisão derruba a conexão em vez de passar.

Onde acontece

No Agente, no caminho de volta. Nada muda no banco — nenhuma view nem coluna a mais —, e a API não vê nem o valor original nem o mascarado.

TERMINALpsql
NOTICE:  Heimdall authenticated this connection as PostgreSQL role "hd_pg_orders_orders_7cyjmwi7_hdm" under access grant 05512517-3276-4918-af87-79ef234fbd9d; any credentials you supplied were not used and were not sent to the database.
psql (18.6, server 17.11)
Type "help" for help.

=> select cpf as documento, upper(email) as e, name from customers limit 1;
 documento |    e    |   name    
-----------+---------+-----------
 [CPF]     | [EMAIL] | Ana Souza
(1 row)
Saída real de uma consulta pelo proxy: cpf com outro nome e email dentro de uma função, os dois mascarados.

O que chega a quem consulta

Estratégias de masking
ESTRATÉGIAO QUE CHEGA
full [REDACTED], ou *** num valor de até três caracteres
partial os dois primeiros e os dois últimos caracteres: an***st
redact um marcador conforme o formato do valor: [EMAIL], [CPF], [PHONE], [CREDIT_CARD] ou [REDACTED]
hash os 16 primeiros caracteres do SHA-256, sem sal: serve para comparar dois valores, mas não para esconder um valor com poucas possibilidades, como um CPF

Quando duas regras casam com a mesma coluna, vence a mais restritiva.

Até onde vai

O masking protege contra a exposição acidental e contra o que a ferramenta mostra: a tela, o arquivo exportado, a captura de tela que vai parar num ticket. Não protege contra quem tem o acesso e escreve SQL para extrair o valor. Para quem não pode ver uma coluna, o controle é o escopo do acesso: o pedido nomeia as tabelas, e um acesso só de SELECT não copia o valor para outra coluna. O masking vale na sessão que passa pelo proxy — heimdall db connect, heimdall tunnel e o break-glass; já a aplicação, com o service user dela, e a migration conectam direto ao banco.

guardrail que recusa antes de chegar ao banco

O DELETE sem WHERE não chega ao banco. Para rodar o que um guardrail recusou, outra pessoa precisa aprovar aquele statement, que então passa uma vez.

A regra é sua

Nenhum guardrail vem instalado. A sua organização escreve cada um e o atribui à organização inteira, a uma instância ou a um banco. As regras cobrem tipos de statement proibidos, como DROP TABLE, TRUNCATE e DROP SCHEMA; UPDATE e DELETE só com WHERE; índice só com CONCURRENTLY; coluna NOT NULL só com DEFAULT; nenhum DROP COLUMN; e um teto de linhas, acima do qual uma tabela não é apagada nem reescrita inteira.

Recusar, avisar ou registrar

Cada guardrail tem uma severidade: block recusa, warn deixa passar e registra, audit_only só registra. Ele é avaliado em três lugares: no proxy, a cada statement da sessão, antes mesmo de conferir o acesso; na submissão de uma migration, que é recusada; e na submissão de um comando, onde não recusa, mas deixa uma anotação para quem aprova.

Lido como o banco lê

O statement é lido sem os comentários e sem o conteúdo dos literais, com as regras de espaço e de quebra de linha do próprio PostgreSQL: um WHERE dentro de um comentário não conta, e um \r não esconde um DROP. EXPLAIN ANALYZE é julgado pelo que executa, e uma escrita dentro de um WITH conta como escrita. O que não dá para julgar pelo texto — DO, CALL, EXECUTE, COPY, MERGE — é recusado por todo guardrail block, e só roda com aprovação.

A recusa ensina o caminho

A recusa nomeia o guardrail e traz pronto o comando que pede a aprovação. A submissão não recusa: ela anota cada guardrail que o texto cruza — inclusive num statement escondido depois de um inofensivo —, e é isso que quem aprova lê antes de decidir. A anotação não muda depois de gravada.

Aprovado, passa uma vez

Aprovar não executa: quem pediu roda o statement na própria sessão, em até 4 horas. Ele passa uma única vez, e a comparação é feita pelo que o servidor executaria, não pelo texto: mover o WHERE para dentro de um comentário transforma o statement em outro comando. A segunda execução é recusada de novo. E a aprovação não amplia o acesso: as tabelas e as operações continuam sendo as do pedido.

TERMINALheimdall db command
Saída real: um UPDATE sem WHERE recusado, submetido com os guardrails que cruza, executado uma vez depois da aprovação e recusado na segunda.

1 · NA SESSÃO, A RECUSA

NOTICE:  Heimdall authenticated this connection as PostgreSQL role "hd_pg_orders_orders_7cyjmwi7_hdm" under access grant 05512517-3276-4918-af87-79ef234fbd9d; any credentials you supplied were not used and were not sent to the database.
psql (18.6, server 17.11)
Type "help" for help.

=> UPDATE customers SET name = 'cliente de teste';
ERROR:  HEIMDALL GUARDRAIL: UPDATE without WHERE clause is blocked by guardrail "DELETE and UPDATE need a WHERE" [require-where]. To run it, get it approved first, then run it again: heimdall db command submit 'b4bc0108-1814-47df-a9a9-7e591423dae6' --reason '<why this must run>' --sql 'UPDATE customers SET name = '\''cliente de teste'\'';'

2 · A SUBMISSÃO ANOTA PARA QUEM APROVA

heimdall db command submit 'b4bc0108-1814-47df-a9a9-7e591423dae6' --reason 'OPS-48: renomear todos os clientes de teste' --sql 'UPDATE customers SET name = '\''cliente de teste'\'';'
✓ Command 2fb2533e-b875-452f-bbd5-50f3f0bab8b8 submitted for database 'pg-orders-orders'
  Status: pending_review
  It runs only after somebody else approves it.
  Guardrails it crosses, shown to the approver:
    block: UPDATE without WHERE clause is blocked by guardrail "DELETE and UPDATE need a WHERE" [require-where]

3 · APROVADO, PASSA UMA VEZ

NOTICE:  Heimdall authenticated this connection as PostgreSQL role "hd_pg_orders_orders_7cyjmwi7_hdm" under access grant 05512517-3276-4918-af87-79ef234fbd9d; any credentials you supplied were not used and were not sent to the database.
psql (18.6, server 17.11)
Type "help" for help.

=> UPDATE customers SET name = 'cliente de teste';
UPDATE 2
=> UPDATE customers SET name = 'cliente de teste';
ERROR:  HEIMDALL GUARDRAIL: UPDATE without WHERE clause is blocked by guardrail "DELETE and UPDATE need a WHERE" [require-where]. To run it, get it approved first, then run it again: heimdall db command submit 'b4bc0108-1814-47df-a9a9-7e591423dae6' --reason '<why this must run>' --sql 'UPDATE customers SET name = '\''cliente de teste'\'';'

Até onde vai

O guardrail julga o texto, sem consultar o catálogo do banco: se uma função que escreve for chamada dentro de um SELECT, quem responde por ela é o privilégio da role, que tem só o que foi pedido. Ele vale na sessão que passa pelo proxy e na submissão de migration e de comando; a aplicação, com o service user dela, não passa por ele. No break-glass, um guardrail que a sua organização marcou como contornável avisa em vez de recusar.

Onde o dado fica

Tudo roda na sua infraestrutura. Mesmo assim, a divisão abaixo importa dentro da sua empresa, porque mostra quem, no seu time, tem acesso a quê.

Database Manager: onde o dado fica
NO AGENTENA API, QUE É SUA

A credencial administrativa de cada instância, no store de secrets. A senha de cada role de acesso, gerada ali. A senha de cada service user. O histórico de cada sessão do proxy. O masking e os guardrails, aplicados ali, sessão a sessão. Quem conecta no banco é sempre o Agente.

O nome de cada banco, schema, tabela e coluna, com a estimativa de linhas e a classificação de cada coluna. O nome de cada role. As regras de masking e de guardrail. O texto de cada comando SQL de uma sessão, e de cada migration e comando submetido, com os guardrails que ele cruza. Cada pedido e cada decisão. O resultado das consultas, não — nem o valor original, nem o mascarado: o proxy não tem como mandá-lo.

Ver a segurança

Quem aprova o quê

Database Manager: quem aprova o quê
ATOQUEM
Pedir acesso, submeter migration ou comando todo membro
Aprovar acesso, comando e migration owner e admin, por padrão — nunca quem pediu. A lista nomeada decide quem é avisado e se o pedido pode ser feito
Escrever guardrail, aprovadores, janelas e prazos owner e admin
Classificar colunas e escrever regras de masking owner e admin
Invocar break-glass só quem uma policy nomear, por exemplo o grupo de plantão. Um banco pode proibi-lo
Revisar um break-glass owner e admin — nunca quem invocou
Revogar um acesso aberto owner e admin

Com os outros módulos

  • 03

    Secret Manager

    A credencial administrativa de cada banco é um secret no Agente; a senha de cada service user nasce e é rotacionada lá.

  • 05

    Acesso a aplicações internas

    A sessão de banco chega pela mesma borda; quem conecta precisa da concessão de rede e da aprovação do banco.

  • 07

    Incident Manager

    Um break-glass invocado com um incidente aberto sobre o banco fica vinculado a ele, sem que ninguém precise digitar o número. O incidente nomeia a entrada, mas nunca a concede.

Converse com quem constrói o Heimdall.

Conte como a sua engenharia guarda secrets e acessa o banco hoje. Respondemos dizendo o que o Heimdall colocaria sob regra primeiro e como a implantação chegaria lá.

Falar com engenharia

Abre uma conversa no WhatsApp. Prefere e-mail? heimdall@yops.cloud