Skip to content
KitploitKITPLOIT
FerramentasExploitsBlog
Log in
Enviar
FerramentasExploitsBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
keeper — Segredo Simples e Seguro | Kitploit
Ferramentas/GitHubGitHub/agberohq/keeper
Autenticação e AutorizaçãoFerramentas de Criptografia/DescriptografiaCriptografiaDetecção de Segredos
GitHubagberohq/keeper

keeper

Segredo Simples e Seguro

Ver Repositório
120415há 5 mesesRevisado pelo Kitploit

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar
# keeper

O Keeper é um armazenamento criptográfico de segredos para Go. Ele criptografa payloads arbitrários de bytes em repouso usando derivação de chave Argon2id e criptografia autenticada XChaCha20-Poly1305 (padrão), e os armazena em um banco de dados bbolt embutido.

Ele é fornecido como três coisas que você pode usar de forma independente:

- **Uma biblioteca Go** — incorpore um armazenamento de segredos reforçado diretamente no seu processo, com quatro níveis de segurança, isolamento de DEK por bucket e uma cadeia de auditoria à prova de violação.
- **Um manipulador HTTP** (`x/keephandler`) — monte endpoints do keeper em qualquer mux `net/http` em uma chamada, com hooks, guardas e codificadores de resposta plugáveis para controle de acesso e registro de auditoria.
- **Uma CLI** (`cmd/keeper`) — uma interface de terminal com sessão REPL persistente, entrada de segredos sem eco e zero exposição no histórico do shell.

O Keeper foi projetado como a camada fundamental de gerenciamento de segredos para o balanceador de carga [Agbero](https://github.com/agberohq/agbero), mas não possui dependência do Agbero e funciona em qualquer projeto Go.

---

## Conteúdo

- [Modelo de segurança](#security-model)
- [Design criptográfico](#cryptographic-design)
- [Hierarquia de chaves](#key-hierarchy)
- [Esquema de armazenamento](#storage-schema)
- [Cadeia de auditoria](#audit-chain)
- [Integração Jack](#jack-integration)
- [x/keepcmd — operações CLI reutilizáveis](#xkeepcmd)
- [x/keephandler — manipulador HTTP](#xkeephandler)
- [Referência da API](#api-reference)
- [Catálogo de erros](#error-catalogue)
- [Decisões de segurança](#security-decisions)
- [Dependências](#dependencies)

---

## Modelo de segurança

O Keeper particiona segredos em buckets. Cada bucket possui uma `BucketSecurityPolicy` imutável que governa como sua Chave de Criptografia de Dados (DEK) é protegida. Quatro níveis estão disponíveis.

### Esquemas vs. Níveis de Segurança

Um **esquema** é um prefixo URI que agrupa buckets relacionados (`vault://`, `certs://`, `space://`, ou qualquer nome que você registre). Um **nível de segurança** é uma propriedade da política do bucket definida na criação e imutável a partir de então.

Você pode misturar níveis de segurança livremente dentro do mesmo esquema. Por exemplo, `vault://system` pode ser `LevelPasswordOnly` (desbloqueado automaticamente na inicialização), enquanto `vault://admin` é `LevelAdminWrapped` (requer credencial explícita).

### LevelPasswordOnly

A DEK do bucket é derivada da chave mestre usando HKDF-SHA256 com uma string de informação separada por domínio por bucket (`keeper-bucket-dek-v1:scheme:namespace`). Todos os buckets `LevelPasswordOnly` são desbloqueados automaticamente quando `UnlockDatabase` é chamada com a frase-senha mestre correta. Nenhuma credencial por bucket é necessária em tempo de execução. Este nível é apropriado para segredos que o processo precisa na inicialização sem interação humana.

### LevelAdminWrapped

O bucket possui uma DEK de 32 bytes gerada aleatoriamente e única para aquele bucket. A DEK nunca é armazenada em texto claro. Para cada administrador autorizado, uma Chave de Criptografia de Chave (KEK) é derivada de `HKDF(masterKey‖adminCred, dekSalt)` e usada para envolver a DEK via XChaCha20-Poly1305. O bucket fica inacessível até que um administrador chame `UnlockBucket` com sua credencial. A frase-senha mestre sozinha não pode descriptografar o bucket. Revogar um administrador não afeta a cópia envolvida de nenhum outro administrador.

### LevelHSM

A DEK do bucket é gerada no momento do `CreateBucket` e imediatamente envolvida por um `HSMProvider` fornecido pelo chamador. O provedor realiza as operações de wrap e unwrap — o keeper nunca manipula a DEK bruta após entregá-la ao provedor. `UnlockDatabase` chama automaticamente o provedor para desembrulhar e semear o Envelope para todos os buckets HSM registrados. A rotação da chave mestre não recriptografa esses buckets; a DEK é controlada pelo provedor.

Uma implementação interna `SoftHSM` apoiada por uma chave de wrapping protegida por memguard está disponível em `pkg/hsm` para ambientes de teste e CI. Não a use em produção.

### LevelRemote

Idêntico ao `LevelHSM` no comportamento de gerenciamento de chaves, mas o `HSMProvider` é implementado por `pkg/remote.Provider` — um adaptador HTTPS configurável que delega wrap e unwrap para qualquer serviço KMS remoto sobre TLS. Configurações pré-construídas para HashiCorp Vault Transit, AWS KMS e GCP Cloud KMS são fornecidas em `pkg/remote`. Para uso em produção, configure `TLSClientCert` e `TLSClientKey` para habilitar autenticação TLS mútua.

---

## Design criptográfico

### Derivação da chave mestre```
salt ← random 32 bytes, generated once, stored as a versioned SaltStore (unencrypted)
masterKey ← Argon2id(passphrase, salt, t=3, m=64 MiB, p=4) → 32 bytes
```
Um hash de verificação é armazenado na primeira derivação:```
verifyHash ← Argon2id(masterKey, "verification", t=1, m=64 MiB, p=4) → 32 bytes
```
Chamadas subsequentes de `DeriveMaster` recalculam este hash e o comparam com `crypto/subtle.ConstantTimeCompare`. Uma incompatibilidade retorna `ErrInvalidPassphrase`.

O salt do KDF é armazenado **não criptografado** por design. Ele deve ser legível antes de `UnlockDatabase` para derivar a chave mestra — criptografá-lo com uma chave derivada da chave mestra seria circular. Um salt do KDF não é um segredo; seu propósito é exclusividade, não confidencialidade.

### Criptografia dos segredos

Cada valor de texto simples é criptografado com XChaCha20-Poly1305 usando a DEK do bucket:```
nonce ← random 24 bytes
ciphertext ← XChaCha20-Poly1305.Seal(nonce, DEK, plaintext)
```
O registro armazenado é uma struct `Secret` codificada em msgpack contendo o texto cifrado, metadados criptografados e a versão do esquema. A autenticação é implícita: um texto cifrado decifrado com a chave errada produz uma falha de autenticação AEAD antes que qualquer texto simples seja retornado.

### Derivação de KEK — LevelAdminWrapped```
salt ← random 32 bytes, generated at bucket creation, stored in policy
ikm ← masterKey ‖ adminCredential
KEK ← HKDF-SHA256(ikm, salt, info="keeper-kek-v1") → 32 bytes
wrappedDEK ← XChaCha20-Poly1305.Seal(nonce, KEK, DEK)
```
A KEK é derivada usando HKDF em vez de uma segunda passagem Argon2. A chave mestra
já foi produzida por um KDF de alto custo; uma segunda invocação Argon2 adicionaria
centenas de milissegundos de latência a cada chamada `UnlockBucket` sem
benefício de segurança. HKDF-SHA256 opera em aproximadamente um microssegundo.

**Defesa em profundidade:** Um atacante que compromete apenas o banco de dados obtém a
DEK encapsulada e o salt HKDF, mas não consegue derivar a KEK sem a chave mestra.
Um atacante que compromete apenas a chave mestra não consegue desencapsular nenhuma
DEK `LevelAdminWrapped` sem também conhecer a credencial de administrador.

### Criptografia de metadados — segredos

Os metadados secretos (hora de criação, hora de atualização, contagem de acesso, versão) são
criptografados separadamente do texto cifrado:```
metaKey ← HKDF-SHA256(bucketDEK, nil, info="keeper-metadata-v1") → 32 bytes
encryptedMeta ← XChaCha20-Poly1305.Seal(nonce, metaKey, msgpack(metadata))
```
Para os buckets `LevelAdminWrapped`, `LevelHSM` e `LevelRemote`, isso significa que os metadados ficam inacessíveis sem a credencial do bucket, impedindo que um invasor com acesso de leitura ao arquivo do banco de dados descubra padrões de acesso ou timestamps.

**Nota sobre canal lateral de temporização:** O XChaCha20-Poly1305 processa o texto cifrado completo antes de retornar um erro de autenticação. O caminho de descriptografia de fallback (nova DEK derivada → antiga chave-mestre-como-DEK) leva o mesmo tempo de relógio de parede, independentemente de qual chave for bem-sucedida. Nenhum canal lateral de temporização vaza o estado de migração de um registro.

### Criptografia de metadados — políticas, WAL e auditoria

Todos os metadados estruturais também são criptografados em repouso. Duas chaves são derivadas da chave mestre no momento de `UnlockDatabase`:```
policyEncKey ← HKDF-SHA256(masterKey, nil, info="keeper-policy-enc-v1") → 32 bytes
auditEncKey  ← HKDF-SHA256(masterKey, nil, info="keeper-audit-enc-v1")  → 32 bytes
```
`policyEncKey` criptografa: os valores de `BucketSecurityPolicy` e o WAL de rotação.

`auditEncKey` criptografa: os campos `Scheme`, `Namespace` e `Details` de
cada evento de auditoria.

Ambas as chaves são limpas da memória em `Lock()`. A cifra usada para criptografia
de metadados é a mesma interface `crypt.Cipher` configurável usada para segredos —
a escolha de cifra do usuário (AES-256-GCM para FIPS, XChaCha20-Poly1305 por padrão)
flui automaticamente.

Formato wire para todos os blobs de metadados criptografados:```
nonce (cipher.NonceSize() bytes) || AEAD-ciphertext
```
### Hashing da chave do bucket de política

As chaves de política no disco são hashes opacos em vez de strings de texto simples `scheme:namespace`, prevenindo a enumeração offline de nomes de bucket:```
base ← hex(SHA-256("scheme:namespace"))[:32]   // 32 hex chars = 128-bit key space
_policies/<base>          → encrypted BucketSecurityPolicy
_policies/<base>__hash__  → SHA-256(encrypted policy bytes)
_policies/<base>__hmac__  → HMAC-SHA256(policyKey, encrypted policy bytes)
```
O `schemeRegistry` em memória continua a usar `"scheme:namespace"` como sua
chave — apenas a representação em disco muda.

### Autenticação de política

Cada registro de política carrega duas tags de integridade escritas atomicamente em uma única transação bbolt.```
hash ← SHA-256(encryptedPolicyBytes)                         — unauthenticated, pre-unlock integrity
policyKey ← HKDF-SHA256(masterKey, nil, info="keeper-policy-hmac-v1") → 32 bytes
hmac ← HMAC-SHA256(policyKey, encryptedPolicyBytes)          — authenticated, post-unlock integrity
```
Antes de `UnlockDatabase`, apenas o hash SHA-256 está disponível. Após o desbloqueio, `loadPolicy` verifica a tag HMAC. `UnlockDatabase` chama `upgradePolicyHMACs` para preencher retroativamente as tags HMAC nas políticas criadas antes da existência deste recurso.

### Auditoria de assinatura HMAC```
auditKey ← HKDF-SHA256(masterKey, nil, info="keeper-audit-hmac-v1") → 32 bytes
HMAC ← HMAC-SHA256(auditKey, event fields including Seq)
```
A chave de assinatura é ativada em `UnlockDatabase` e limpa em `Lock`. Quando a chave mestra é rotacionada, `Rotate` anexa um evento de checkpoint de rotação de chave a cada cadeia de auditoria ativa, assinado com a chave de auditoria antiga como o evento final da época antiga. O histórico nunca é reescrito; o checkpoint é a ponte de confiança entre as épocas.

---

## Hierarquia de chaves```
passphrase
    │
    └─ Argon2id(salt) ──→ masterKey (32 bytes, memguard Enclave)
                              │
                              ├─ HKDF("keeper-audit-hmac-v1")  ──→ auditKey    (HMAC signing)
                              ├─ HKDF("keeper-audit-enc-v1")   ──→ auditEncKey (audit field encryption)
                              ├─ HKDF("keeper-policy-hmac-v1") ──→ policyKey   (policy HMAC)
                              ├─ HKDF("keeper-policy-enc-v1")  ──→ policyEncKey (policy/WAL encryption)
                              │
                              ├─ [LevelPasswordOnly]
                              │       └─ HKDF("keeper-bucket-dek-v1:scheme:ns") ──→ DEK
                              │               └─ HKDF("keeper-metadata-v1") ──→ metaKey
                              │
                              ├─ [LevelAdminWrapped]
                              │       ├─ random 32 bytes ──→ DEK
                              │       │       └─ HKDF("keeper-metadata-v1") ──→ metaKey
                              │       │
                              │       └─ HKDF("keeper-kek-v1", masterKey‖adminCred, dekSalt)
                              │                 └─ KEK
                              │                       └─ XChaCha20-Poly1305(KEK, DEK) ──→ wrappedDEK
                              │
                              └─ [LevelHSM / LevelRemote]
                                      ├─ random 32 bytes ──→ DEK
                                      │       └─ HKDF("keeper-metadata-v1") ──→ metaKey
                                      │
                                      └─ HSMProvider.WrapDEK(DEK) ──→ wrappedDEK
                                         (stored; provider controls the wrapping key)
```
Todas as chaves intermediárias são zeradas imediatamente após o uso. A chave mestre nunca é gravada no disco de nenhuma forma.

---

## Esquema de armazenamento

O banco de dados subjacente é o bbolt. Todos os buckets e seus conteúdos:

| bbolt bucket | Chave | Valor |
|---|---|---|
| `__meta__` | `salt` | msgpack — SaltStore (não criptografado; dependência circular se criptografado) |
| `__meta__` | `verify` | bytes brutos — hash de verificação Argon2id |
| `__meta__` | `rotation_wal` | `nonce‖AEAD(msgpack(RotationWAL))` |
| `__meta__` | `bucket_dek_done` | `"1"` — marcador de conclusão de migração DEK |
| `__policies__` | `hex(SHA-256(scheme:ns))[:32]` | `nonce‖AEAD(msgpack(BucketSecurityPolicy))` |
| `__policies__` | `<base>__hash__` | hex SHA-256 dos bytes da política criptografada |
| `__policies__` | `<base>__hmac__` | hex HMAC-SHA256(policyKey, bytes da política criptografada) |
| `__audit__/scheme/namespace` | UUID do evento | JSON — Event de auditoria |
| `__audit__/scheme/namespace` | `__chain_index__` | JSON — chainIndex |
| `scheme/namespace` | string de chave | msgpack — struct Secret |

### Estrutura Secret (msgpack)```go
type Secret struct {
    Ciphertext    []byte `msgpack:"ct"`
    EncryptedMeta []byte `msgpack:"em,omitempty"`
    SchemaVersion int    `msgpack:"sv"`  // always 1
}
```
### Campos do Evento de Auditoria

A struct `Event` usa campos de roteamento em texto simples separados (`Scheme`,
`Namespace`) juntamente com campos de payload criptografados (`EncScheme`, `EncNamespace`,
`EncDetails`). Checksums são calculados sobre os campos de roteamento em texto simples e os
bytes criptografados de `EncDetails`, para que a integridade da cadeia possa ser verificada em três níveis
sem qualquer chave:

| Nível | Possui | Pode verificar |
|---|---|---|
| Público | Nada | Cadeia de checksum SHA-256 (detecta adulteração e inserção) |
| Detentor da chave de auditoria | `auditEncKey` | Cadeia completa + descriptografar Scheme/Namespace/Details |
| Operador | Frase secreta principal | Tudo |

**Exemplo:** Um auditor de conformidade recebe apenas `auditEncKey`. Ele pode verificar
toda a cadeia HMAC em rotações de chave e ler todos os detalhes do evento, mas não pode
descriptografar nenhum valor secreto. Um observador público com apenas o arquivo de banco de dados pode
ainda detectar se algum evento foi modificado ou inserido posteriormente.

### Repositório de salt versionado

O salt KDF é armazenado como um `SaltStore` codificado em msgpack sob a chave de metadados `salt`.
Cada rotação de salt acrescenta uma nova `SaltEntry` e avança
`CurrentVersion`. Entradas antigas são retidas como trilha de auditoria. O SaltStore é
armazenado não criptografado — veja [Decisões de segurança](#security-decisions).

### WAL de rotação à prova de falhas

`Rotate` escreve um WAL antes de tocar em qualquer registro. O WAL carrega
`WrappedOldKey`: a chave mestra pré-rotação criptografada com a nova chave mestra.
Após uma falha, a frase secreta antiga se foi; `WrappedOldKey` é a única maneira correta
de transportar a chave antiga através do limite. Em `UnlockDatabase`, quando um WAL
está presente, a nova chave mestra descriptografa `WrappedOldKey` e a rotação retoma
a partir do cursor do WAL. O próprio WAL é criptografado com `policyEncKey`.

---

## Cadeia de auditoria

Cada operação significativa acrescenta um evento à prova de adulteração à cadeia de auditoria do bucket.
A integridade da cadeia depende de dois mecanismos.

**Checksum.** SHA-256 sobre prevChecksum, ID, BucketID, Scheme, Namespace,
EncDetails, EventType e Timestamp. Usar `Scheme`/`Namespace` como texto simples
(sempre preservados junto com as formas criptografadas) garante que o checksum seja estável
em todos os caminhos de carregamento. `EncDetails` fornece integridade sobre o payload criptografado.

**HMAC.** HMAC-SHA256 sobre todos os campos, incluindo Seq. Um atacante que pode escrever
no banco de dados, mas não conhece a chave de auditoria, não pode produzir um HMAC válido.
`VerifyIntegrity` verifica ambas as camadas para cada evento.

**Limite de época de rotação de chave.** Em `Rotate`, um evento de checkpoint é acrescentado
a cada cadeia ativa, carregando fingerprints das chaves de auditoria de saída e de entrada.
O checkpoint é assinado com a chave de saída. Auditores que possuem
qualquer chave de época podem recuperar chaves de época subsequentes do campo `wrapped_new_key`
e verificar a continuidade do HMAC em toda a cadeia.

**Poda automática.** Quando `AuditPruneInterval` está definido em `Config`, um
`jack.Scheduler` é executado periodicamente e chama `PruneEvents` em cada bucket
registrado. Buckets `LevelHSM` e `LevelRemote` nunca são podados, independentemente
desta configuração.

---

## Integração Jack

Jack é uma biblioteca opcional de supervisão de processos. Quando um `JackConfig` é
fornecido via `WithJack`, o keeper ativa componentes em segundo plano automaticamente:

- **Auto-lock Looper:** Verifica periodicamente o timestamp da última atividade e
  descarta DEKs de bucket `LevelAdminWrapped` após `AutoLockInterval`. Buckets `LevelPasswordOnly`
  permanecem desbloqueados para que tarefas em segundo plano continuem ininterruptas. O
  padrão de bloqueio de escrita única dentro da tarefa looper elimina a condição de corrida `RUnlock→Lock`
  presente em projetos anteriores.
- **Per-bucket DEK Reaper:** Expiração baseada em TTL para DEKs `LevelAdminWrapped`.
- **Pacientes de monitoramento de saúde:** Verificação de latência de leitura bbolt e verificação de ida e volta de criptografia/descriptografia,
  ambos registrados com `jack.Doctor`.
- **Agendador de poda de auditoria:** `PruneEvents` periódico em todos os buckets que não sejam HSM.
- **Pool de eventos assíncrono:** Eventos de auditoria enviados sem bloquear a operação principal.

Se `JackConfig` não for fornecido, o keeper é executado sem essas tarefas em segundo plano.
Keeper nunca chama `pool.Shutdown` — o ciclo de vida do pool pertence ao chamador.

---

## x/keepcmd

`x/keepcmd` fornece operações keeper reutilizáveis desacopladas de qualquer estrutura CLI.
Embuta-o no seu próprio aplicativo para obter gerenciamento de segredos tipado e testável
sem puxar o binário CLI.```go
import "github.com/agberohq/keeper/x/keepcmd"

cmds := &keepcmd.Commands{
    Store: func() (*keeper.Keeper, error) {
        return security.KeeperOpen(cfg)  // your own config
    },
    Out:     keepcmd.PlainOutput{},
    NoClose: false, // true in REPL / session contexts
}

cmds.List()                                          // all keys: scheme://namespace/key
cmds.List("vault")                                   // all keys in scheme vault
cmds.List("vault", "system")                         // all keys in vault://system
cmds.Get("vault://system/jwt_secret")
cmds.Set("vault://system/jwt_secret", "newsecret", keepcmd.SetOptions{})
cmds.Rotate(newPassphraseBytes)    // caller resolved the passphrase — no prompter dependency
cmds.RotateSalt(currentPassBytes)  // same
```
`keepcmd` nunca chama `prompter` ou lê de stdin. A resolução da frase secreta
é inteiramente responsabilidade do chamador — isso mantém o pacote seguro em
contextos de servidor headless.

`NoClose: true` impede que `Commands` chame `store.Close()` após cada
operação. Use isso em contextos de REPL / sessão onde um único store é compartilhado
entre muitas chamadas.

---

## x/keephandler

`x/keephandler` monta endpoints HTTP do keeper em qualquer mux `net/http`. Sem
dependência de roteador externo — usa roteamento método+padrão do Go 1.22+ com
stdlib `http.ServeMux`.```go
import "github.com/agberohq/keeper/x/keephandler"

keephandler.Mount(mux, store,
    keephandler.WithPrefix("/api/keeper"),
    keephandler.WithGuard(func(w http.ResponseWriter, r *http.Request, route string) bool {
        if !acl.Allow(r.Header.Get("X-Principal"), route) {
            http.Error(w, `{"error":"forbidden"}`, http.StatusForbidden)
            return false
        }
        return true
    }),
    keephandler.WithHooks(
        keephandler.Hook{
            Route:       keephandler.RouteGet,
            CaptureBody: false,
            After: func(r *http.Request, status int, _ []byte) {
                audit.Log(r.Context(), route, status)
            },
        },
    ),
    keephandler.WithEncoder(func(w http.ResponseWriter, route string, status int, data any) {
        w.Header().Set("Content-Type", "application/json")
        w.WriteHeader(status)
        json.NewEncoder(w).Encode(map[string]any{
            "ok":    status < 400,
            "route": route,
            "data":  data,
        })
    }),
    keephandler.WithRoutes(func(m *http.ServeMux) {
        m.HandleFunc("POST /api/keeper/totp/{user}", myTOTPHandler)
    }),
)
```
### Endpoints

| Method | Path | Descrição |
|---|---|---|
| `POST` | `{prefix}/unlock` | Desbloquear o cofre com uma frase secreta |
| `POST` | `{prefix}/lock` | Bloquear o cofre |
| `GET` | `{prefix}/status` | Estado do bloqueio — seguro consultar sem autenticação |
| `GET` | `{prefix}/keys` | Listar todas as chaves secretas |
| `GET` | `{prefix}/keys/{key}` | Recuperar um valor secreto |
| `POST` | `{prefix}/keys` | Armazenar um segredo (JSON ou multipart) |
| `DELETE` | `{prefix}/keys/{key}` | Excluir um segredo |
| `POST` | `{prefix}/rotate` | Rotacionar a frase secreta mestre |
| `POST` | `{prefix}/rotate/salt` | Rotacionar o salt KDF |
| `GET` | `{prefix}/backup` | Transmitir um instantâneo do banco de dados |

### Contrato do Hook

`BeforeFunc` retorna `(allow bool, err error)`.

- `(true, nil)` — permitir que a requisição prossiga.
- `(false, nil)` — abortar; o hook já escreveu uma resposta completa.
- `(false, err)` — abortar; o framework escreve um `500` usando `err.Error()`. O hook **não** deve ter escrito nada em `w`.

`Hook.CaptureBody bool` controla se `AfterFunc` recebe o corpo da resposta. `false` (padrão) custa um wrapper `statusWriter` leve; `true` armazena o corpo completo em um `bytes.Buffer` para o `AfterFunc` — uma alocação por requisição.

Os hooks são executados na ordem de registro. Múltiplas chamadas `WithHooks` são aditivas. Apenas o primeiro hook registrado para um determinado nome de rota é usado—registros subsequentes para a mesma rota são ignorados.

---

## Referência da API

### Construção e desbloqueio```go
store, err := keeper.New(keeper.Config{
    DBPath:              "/var/lib/agbero/keeper.db",
    AutoLockInterval:    30 * time.Minute,
    EnableAudit:         true,
    AuditPruneInterval:  24 * time.Hour,
    AuditPruneKeepLastN: 10_000,
    AuditPruneOlderThan: 90 * 24 * time.Hour,
    DBLatencyThreshold:  200 * time.Millisecond,
    Logger:              logger,
}, keeper.WithJack(keeper.JackConfig{
    Pool:     jackPool,
    Shutdown: jackShutdown,
}))
defer store.Close()

// Shorthand (wraps DeriveMaster + UnlockDatabase):
if err := store.Unlock([]byte(os.Getenv("KEEPER_PASSPHRASE"))); err != nil {
    log.Fatal(err) // ErrInvalidPassphrase on wrong passphrase
}
```
`UnlockDatabase` executa o seguinte, em ordem:

1. Deriva e ativa a chave de assinatura HMAC de auditoria
2. Deriva e ativa a chave HMAC de política
3. Deriva e ativa `policyEncKey` e `auditEncKey`
4. Limpa e recarrega `schemeRegistry` (descriptografa todos os blobs de política)
5. Retoma qualquer WAL de rotação interrompida
6. Atualiza tags HMAC de política
7. Propaga todos os DEKs de bucket `LevelPasswordOnly` para o Envelope
8. Inicia tarefas em segundo plano (loop de migração, bloqueio automático, pacientes de saúde)

### Bucket `LevelPasswordOnly` — ciclo de vida completo```go
err := store.CreateBucket("vault", "system", keeper.LevelPasswordOnly, "init")

store.Set("vault://system/jwt_secret", []byte("supersecret"))
val, err := store.Get("vault://system/jwt_secret")

// Namespaced convenience wrappers
store.SetNamespaced("admin", "jwt_secret", secretBytes)
val, err = store.GetNamespaced("admin", "jwt_secret")
```
### LevelAdminWrapped bucket — ciclo de vida completo```go
err := store.CreateBucket("finance", "payroll", keeper.LevelAdminWrapped, "ops-team")
err = store.AddAdminToPolicy("finance", "payroll", "alice", []byte("alicepass"))

store.SetNamespacedFull("finance", "payroll", "salary_key", []byte("AES256..."))

store.LockBucket("finance", "payroll")
err = store.UnlockBucket("finance", "payroll", "bob", []byte("bobpass"))
// ErrAuthFailed — does not distinguish wrong password from unknown admin (CWE-204)

err = store.RevokeAdmin("finance", "payroll", "alice")
err = store.RotateAdminWrappedDEK("finance", "payroll", "bob", []byte("bobpass"))

needs, err := store.NeedsAdminRekey("finance", "payroll")
```
### LevelHSM / LevelRemote baldes```go
import (
    "github.com/agberohq/keeper/pkg/hsm"
    "github.com/agberohq/keeper/pkg/remote"
)

// SoftHSM — testing only
provider, _ := hsm.NewSoftHSM()
store.RegisterHSMProvider("secure", "keys", provider)
store.CreateBucket("secure", "keys", keeper.LevelHSM, "ops")

// Vault Transit
cfg := remote.VaultTransit("https://vault.corp:8200", vaultToken, "my-key")
cfg.TLSClientCert = "/etc/keeper/client.crt"
cfg.TLSClientKey  = "/etc/keeper/client.key"
provider, _ = remote.New(cfg)
store.RegisterHSMProvider("tenant", "secrets", provider)
store.CreateBucket("tenant", "secrets", keeper.LevelRemote, "ops")
```
### Exportação de chave de auditoria```go
// Export the audit encryption key to allow a third-party auditor to decrypt
// event details without access to the master passphrase.
auditKey, err := store.ExportAuditKey()
defer zero.Bytes(auditKey)

events, err := auditStore.LoadChain("vault", "system", auditKey)
```
### Rotação de chaves```go
// Rotate passphrase — crash-safe WAL, resumes on next Unlock if interrupted
store.Rotate([]byte("new-passphrase"))

// Rotate KDF salt — re-derives master key, re-encrypts LevelPasswordOnly
store.RotateSalt([]byte("current-passphrase"))
```
### Comparar e trocar```go
err := store.CompareAndSwapNamespacedFull("vault", "system", "counter",
    []byte("old"), []byte("new"))
// ErrCASConflict if current value does not match old
```
### Cópia de segurança```go
f, _ := os.Create("keeper.db.bak")
info, err := store.Backup(f)
// info.Bytes, info.Timestamp, info.DBPath
```
---

## Catálogo de erros

Todos os erros sentinela funcionam com `errors.Is` e `errors.As`. Rastreamentos de pilha são
capturados no ponto de criação via `github.com/olekukonko/errors`.

| Erro | Significado |
|---|---|
| `ErrStoreLocked` | Operação tentada enquanto o armazenamento está bloqueado |
| `ErrInvalidPassphrase` | Frase-passe mestre incorreta |
| `ErrAuthFailed` | Qualquer falha de `UnlockBucket` — não distingue senha errada de ID de administrador desconhecido (CWE-204) |
| `ErrKeyNotFound` | Chave secreta não existe |
| `ErrBucketLocked` | Bucket não foi desbloqueado |
| `ErrPolicyImmutable` | Segunda política para um bucket existente |
| `ErrPolicyNotFound` | Nenhuma política para o esquema/espaço de nomes fornecido |
| `ErrAdminNotFound` | ID de administrador não está na política — apenas `RevokeAdmin` |
| `ErrHSMProviderNil` | Bucket HSM/Remoto criado sem um provedor registrado |
| `ErrCheckLatency` | Latência de leitura do BD excedeu `DBLatencyThreshold` |
| `ErrCASConflict` | Valor atual não corresponde ao esperado em `CompareAndSwap` |
| `ErrSecurityDowngrade` | Movimento entre buckets de nível de segurança superior para inferior |
| `ErrAlreadyUnlocked` | `UnlockDatabase` chamado em um armazenamento já desbloqueado |
| `ErrMasterRequired` | `UnlockDatabase` chamado com `Master` nulo ou destruído |
| `ErrChainBroken` | Verificação de integridade da cadeia de auditoria falhou |
| `ErrMetadataDecrypt` | Metadados criptografados não puderam ser descriptografados |
| `ErrPolicySignature` | Verificação HMAC da política falhou — o registro foi adulterado |

---

## Decisões de segurança

**ErrAuthFailed unifica todas as falhas de UnlockBucket (CWE-204 / CVSS 5.3).** Tanto
um ID de administrador desconhecido quanto uma senha errada retornam `ErrAuthFailed`. Isso impede
enumeração de ID de administrador por tempo ou comparação de string de erro. `RevokeAdmin` mantém
`ErrAdminNotFound` porque é uma operação administrativa em um armazenamento já
desbloqueado. A comparação em tempo constante para presença de ID de administrador é
omitida intencionalmente. Um atacante que consiga medir diferenças de submicrossegundos
em consultas de bucket bbolt precisaria de acesso local ao sistema de arquivos — ponto no qual
pode ler o bucket de políticas diretamente. O modelo de ameaça assume que o arquivo do banco de dados
pode estar comprometido; a defesa de tempo contra enumeração remota é a principal
preocupação.

**Argon2id domina o tempo.** Argon2id leva 200–500 ms em hardware típico.
Diferenças de comparação pós-derivação são quatro ou mais ordens de grandeza
menores e não são mensuráveis remotamente. Nenhuma equalização artificial é aplicada.

**DEK recuperado dentro do limite da transação CAS.** `CompareAndSwapNamespacedFull`
recupera o DEK do bucket dentro da transação de escrita do bbolt, eliminando a
janela onde um `Rotate` concorrente poderia alterar o DEK entre a recuperação e o
uso.

**Frase-passe nunca armazenada como string Go no manipulador HTTP.** Todos os três
campos de frase-passe (`passphrase`, `new_passphrase`) são decodificados de JSON
diretamente para `[]byte` via extração de mapa bruto, mantendo o array de suporte da string
fora do heap de longa duração. A cópia `[]byte` é zerada com `wipeBytes` após o uso.

**Nenhuma flag `--passphrase` na CLI.** Flags aparecem na saída do `ps` e no histórico do shell.
A CLI aceita a frase-passe apenas da variável de ambiente `KEEPER_PASSPHRASE` ou
de um prompt interativo sem eco.

**Valores secretos do REPL nunca são visíveis.** `set <key>` no REPL sem um
valor inline usa `term.ReadPassword` — não aparece no histórico de rolagem do terminal,
histórico do shell ou `ps`. Um valor inline (`set key valor`) pode ser
fornecido para dados não sensíveis quando conveniente.

**SaltStore é intencionalmente não criptografado.** O sal do KDF deve ser legível
antes de `UnlockDatabase` para derivar a chave mestre. `policyEncKey` (usada para toda
outra criptografia de metadados) é ela própria derivada da chave mestre — criptografar
o sal com `policyEncKey` seria circular. Um sal de KDF fornece exclusividade,
não confidencialidade; não há valor de segurança em criptografá-lo.

**Chaves do bucket de políticas são hash, não texto simples.** As chaves de políticas em disco são
`hex(SHA-256("scheme:namespace"))[:32]` — 128 bits de espaço de chave — em vez de
strings legíveis. Um atacante offline que leia o arquivo bbolt não pode enumerar
nomes de bucket sem descriptografar os blobs de políticas.

**Criptografia de metadados usa a mesma interface de cifra que segredos.** Todas as
operações `policyEncKey` e `auditEncKey` passam por `s.config.NewCipher(key)`
— a mesma interface `crypt.Cipher` configurada para valores secretos. A escolha de
cifra do usuário (AES-256-GCM para FIPS 140, XChaCha20-Poly1305 por padrão) flui
para criptografia de políticas, WAL e auditoria automaticamente. Nenhum caminho de código
define um algoritmo específico.

**Buckets LevelHSM e LevelRemote ignorados durante rotação da chave mestre.**
`reencryptAllWithKey` e `RotateSalt` ignoram explicitamente esses buckets. O DEK
é controlado pelo provedor; a rotação do sal mestre não o afeta.

**Rotação segura contra falhas com WrappedOldKey.** `Rotate` escreve um WAL antes
de tocar em qualquer registro. O WAL carrega `WrappedOldKey`: a chave mestre pré-rotação
criptografada com a nova chave mestre. Após uma falha, `UnlockDatabase` descriptografa
`WrappedOldKey` usando a nova chave verificada e retoma a rotação a partir do cursor.

---

## Dependências

| Pacote | Propósito |
|---|---|
| `go.etcd.io/bbolt` | Armazenamento chave-valor embutido |
| `golang.org/x/crypto` | Argon2id, XChaCha20-Poly1305, HKDF, scrypt |
| `github.com/awnumar/memguard` | Enclave de chave seguro na memória (chave mestre, DEKs) |
| `github.com/vmihailenco/msgpack/v5` | Serialização binária para segredos e políticas |
| `github.com/olekukonko/jack` | Supervisão de processos (integração opcional com Jack) |
| `github.com/olekukonko/ll` | Registro estruturado |
| `github.com/olekukonko/errors` | Erros sentinela com rastreamento de pilha |
| `github.com/olekukonko/zero` | Zeramento seguro de fatias de bytes |
| `github.com/olekukonko/prompter` | Prompts de terminal sem eco (apenas CLI) |
| `github.com/integrii/flaggy` | Análise de flags de CLI (apenas cmd/keeper) |
| `golang.org/x/term` | Detecção de TTY e leitura de senha bruta (apenas CLI) |
Baixar ferramenta