# Os testes de canal lateral de tempo devem tratar a recusa como um resultado

Um agente não precisa de um valor secreto para descobrir algo perigoso. Se puder chamar repetidamente um gateway de ações e separar os resultados pela duração, talvez descubra se o cofre está bloqueado, se um nome de credencial existe, se uma requisição passou pela autorização ou se o gateway chegou a um serviço remoto.

Essa informação já basta para mudar seu comportamento. Um agente de programação que descobre a existência de uma credencial pode continuar pedindo acesso a esse serviço. Um agente comprometido pode usar o resultado para reconhecimento. Em uma única chamada, o vazamento pode parecer inofensivo. Ele se torna útil quando quem chama controla as entradas, repete as requisições e consegue medir o relógio com precisão.

A MITRE registra essa classe como CWE-208, discrepância de tempo observável. A descrição é adequadamente ampla: o estado interno relevante para a segurança pode escapar quando as operações levam tempos observavelmente diferentes. O exemplo habitual é a verificação de senhas. Um gateway de agentes tem uma versão menos conhecida do mesmo problema: quem chama já está dentro do fluxo de trabalho do desenvolvedor e pode fazer muitas chamadas estruturadas de ferramentas sem se cansar.

A solução não é fazer toda ação levar exatamente o mesmo tempo. Essa promessa costuma ser falsa, especialmente quando uma requisição HTTP ou uma conexão SSH sai da máquina. O trabalho é mais restrito e prático: identificar estados que um agente não deveria conseguir distinguir, tornar comparável o tratamento local deles e manter uma suíte de testes diferenciais que detecte novos caminhos rápidos.

## Trate o estado do cofre como um dado que quem chama não deve inferir

Um cofre bloqueado, uma credencial ausente, uma ação recusada e uma credencial remota inválida são condições operacionais diferentes. Isso não significa que sejam fatos diferentes que o agente precise conhecer.

Comece registrando o que cada chamador pode saber. Parece óbvio, mas muitas equipes pulam essa etapa e deixam a ordem da implementação decidir a política de divulgação. Uma consulta antecipada ao cofre expõe a existência da credencial. Uma verificação antecipada de autorização expõe se um processo tem uma sessão aprovada. Um erro local imediato expõe que o cofre está bloqueado. Cada atalho pode ser razoável isoladamente. Juntos, eles oferecem ao chamador uma sonda de estado.

Para um gateway de ações, separe estas perguntas:

- Este processo pode pedir uma ação?
- O cofre está disponível para executá-la?
- Existe um mapeamento de credencial para essa ação?
- O serviço remoto aceitou a credencial?
- O chamador pode ver alguma dessas respostas diretamente ou recebe apenas o resultado da ação?

A distinção importante é entre **estado de autorização** e **estado da credencial**. A autorização responde se uma execução específica do agente pode invocar uma ação. O estado da credencial responde se o gateway tem o material necessário para executá-la. Se você misturar esses estados internamente, um resultado de tempo pode informar acidentalmente a um agente que existe uma credencial apenas porque o gateway verificou sua existência depois da aprovação.

O bloqueio do cofre do Sallyport é intencionalmente absoluto: enquanto o cofre está bloqueado, toda ação é recusada. É um limite de segurança claro, mas ainda merece testes de tempo, pois quem chama pode observar como a recusa é produzida. O objetivo não é fingir que um cofre bloqueado pode concluir uma requisição remota. O objetivo é garantir que o tratamento local da recusa não crie uma assinatura fácil e repetível que revele mais do que o chamador precisa.

A OWASP defende o mesmo ponto em seu Authentication Cheat Sheet. Um texto de erro genérico, sozinho, não encerra um vazamento de enumeração se um caminho de falha executar mais trabalho que outro. A orientação trata de autenticação, mas a lição de engenharia se aplica aqui: uma mensagem uniforme envolvendo caminhos de execução diferentes ainda vaza informação pelo tempo decorrido.

## Quatro resultados exigem dois contratos de tempo diferentes

Tentar igualar todos os resultados é uma recomendação popular porque é simples de explicar. Ela também está errada quando o gateway pode chamar redes. Você precisa de dois contratos de tempo, não de uma duração universal impossível.

O primeiro contrato cobre os **resultados locais**. São estados em que o gateway deve recusar antes de enviar uma requisição: o cofre está bloqueado, a autorização da sessão está ausente ou foi negada, uma ação não tem credencial configurada ou uma aprovação por chamada foi recusada. Para estados que devem revelar a mesma quantidade de informação ao agente, use um envelope comum de resposta local.

O segundo contrato cobre os **resultados enviados**. Uma credencial válida e uma inválida podem chegar ao mesmo servidor HTTP de teste ou host SSH de teste. O tempo total inclui transporte, reutilização da conexão, trabalho do servidor e entrega da resposta. Não é possível tornar a internet constante no tempo. É possível garantir que o gateway injete, envie, registre e mapeie os dois resultados por caminhos comparáveis. Depois, use um servidor controlado para manter estável a parte remota durante o teste.

Isso produz uma matriz útil:

| Resultado | A requisição sai do gateway? | Com o que o teste de tempo deve compará-lo? |
| --- | --- | --- |
| Cofre bloqueado | Não | Outras recusas locais que não devem revelar mais estado |
| Autorização negada | Não | Outras recusas locais, medidas antes da interação humana |
| Credencial ausente | Não | Falha local com o cofre disponível, usando o mesmo envelope de resposta quando a política de divulgação exigir |
| Credencial inválida | Sim | Credencial válida contra o mesmo serviço upstream controlado |
| Credencial válida | Sim | Credencial inválida e variantes de erro remoto |

Não compare diretamente uma credencial ausente com uma chamada bem-sucedida a uma API de terceiros e declare o teste reprovado porque uma é mais rápida. Esse teste apenas prova que uma requisição que nunca saiu da máquina é mais rápida que uma requisição na internet. Você já sabia disso.

Em vez disso, teste os limites separadamente. Um chamador local deve ter dificuldade para separar os estados locais que você pretende esconder. Um chamador que envia requisições deve ter dificuldade para separar uma credencial de teste boa de uma ruim com base no overhead do gateway. O servidor de teste pode retornar códigos de status diferentes intencionalmente depois do mesmo atraso, para que o resultado semântico continue diferente e o experimento de tempo permaneça útil.

Existe um limite inegociável: se o chamador recebe um erro detalhado dizendo «credencial ausente», ele já não precisa do tempo para saber que a credencial está ausente. Defesas temporais não conseguem salvar uma API que informa livremente o estado secreto. Primeiro decida quais semânticas de erro são permitidas.

## Uma falha rápida vira um oráculo quando pode ser repetida

Um vazamento de tempo raramente aparece como uma diferença dramática. Com mais frequência, alguém adiciona um retorno antecipado sensato durante uma refatoração.

Imagine um gateway com esta ordem de operações:

1. Analisa a requisição do agente.
2. Procura a credencial nomeada no índice do cofre.
3. Verifica se o processo do agente tem aprovação da sessão.
4. Monta e envia a requisição.

Uma credencial ausente sai na etapa dois. Uma credencial existente com um processo não aprovado continua até a etapa três. A resposta externa pode ser idêntica nos dois casos: «ação indisponível». Ainda assim, o segundo caminho teve um acerto no índice do cofre, uma consulta de autorização, preparação da auditoria e talvez a criação de um cartão de aprovação. O chamador pode executar cada entrada muitas vezes e ordenar os resultados.

O vazamento piora quando o agente pode escolher aliases de credenciais. Ele pode testar uma lista de nomes como `staging`, `production`, `deploy` ou nomes encontrados em um repositório. Não precisa executar uma ação com sucesso. Basta ter um grupo rápido e outro lento.

A ordem defensiva depende da sua política de divulgação, mas um formato mais seguro é:

1. Valide a requisição sem ramificações dependentes de segredos.
2. Aplique o bloqueio absoluto do cofre.
3. Aplique a autorização do processo ou da sessão.
4. Resolva a ação e a credencial somente depois que o chamador passar pelos controles que devem preceder esse conhecimento.
5. Passe as falhas locais que precisam permanecer indistinguíveis pelo mesmo trabalho de registro, tratamento de erro e conclusão da resposta.

Não interprete isso como uma instrução para executar trabalho secreto para um processo não autorizado. Você não deve descriptografar uma credencial nem construir um cabeçalho de autorização real apenas para desperdiçar tempo. O objetivo é evitar atalhos dependentes do estado no limite observável, não transformar recusas em acesso a segredos.

Um ajuste ruim comum é `sleep(100ms)` antes de toda recusa. Ele deixa o gráfico de uma demonstração mais bonito e cria três problemas. A duração ainda costuma variar com o ruído do escalonador e o comportamento do cache. O atraso prejudica usuários legítimos. Mais importante, um chamador que repete as tentativas pode eliminar pela média o preenchimento aleatório ou fixo se os caminhos subjacentes continuarem diferentes. Igualar o trabalho necessário é mais confiável que espalhar atrasos sobre trabalhos desiguais.

## Meça no limite que o agente realmente observa

A instrumentação dentro do gateway é útil para diagnóstico, mas não é a principal medição de segurança. O agente observa o tempo entre enviar uma chamada de ferramenta e receber seu resultado final. É aí que o teste diferencial de ponta a ponta começa e termina.

Use um driver de teste dedicado que se comporte como um cliente de agente. Ele deve começar com uma identidade de processo conhecida, enviar uma requisição, esperar uma resposta completa, registrar uma duração monotônica e armazenar o rótulo do resultado fora da requisição medida. Não imprima rótulos durante a execução. Saída no console, exportadores de tracing e logs de depuração podem distorcer caminhos locais curtos o bastante para esconder a regressão que você procura.

Um registro de amostra prático precisa de mais que um número:

```json
{
  "case": "vault_locked",
  "sequence": 184,
  "elapsed_us": 12746,
  "result_class": "local_denial",
  "connection_mode": "fresh",
  "run_id": "test-run-7"
}
```

Use um relógio monotônico. Relógios de parede saltam quando ocorre sincronização de tempo e são instrumentos ruins para comparações abaixo de um segundo. Registre microssegundos ou nanossegundos se a plataforma oferecer essa opção, e depois mostre milissegundos quando as pessoas lerem os resultados. Precisão no arquivo não significa exatidão da medição, mas evita descartar informação antes da análise.

Execute uma fase de aquecimento antes de coletar amostras. As primeiras chamadas podem iniciar o aplicativo, carregar código, inicializar um identificador do cofre, criar um pool de conexões ou preencher um cache. Esses efeitos são reais do ponto de vista operacional, mas costumam dominar a diferença de estado estável que você precisa examinar. Teste o início a frio separadamente se ele for visível aos agentes. Não descarte silenciosamente o comportamento inconveniente da primeira chamada só porque ele deixa os gráficos menos bonitos.

Randomize a ordem dos casos. Se executar 500 chamadas com o cofre bloqueado e depois 500 chamadas com credenciais ausentes, o estado térmico, a coleta de lixo, a atividade em segundo plano e a reutilização de conexões se tornam fatores de confusão. Intercale os casos usando uma mistura com semente definida, para que as falhas possam ser reproduzidas.

Alterne também entre conexões novas e reutilizadas quando o protocolo do cliente permitir as duas opções. Um vazamento que desaparece em uma conexão aquecida ainda pode importar se um agente criar clientes de curta duração. Um vazamento que aparece apenas com reutilização pode expor um cache indexado pela existência da credencial.

## Construa um upstream controlado que separe semântica de atraso

Credenciais inválidas são o caso de teste que as equipes mais costumam fazer errado. Elas apontam o harness de teste para um serviço real, enviam um token deliberadamente ruim e o comparam com uma chamada bem-sucedida. Limites de taxa do provedor, roteamento regional, retomada de sessão TLS e controles contra abuso passam a fazer parte do resultado. Isso não é um teste de tempo do gateway.

Construa um pequeno servidor de fixture sob seu controle. Ele deve ler uma credencial de teste conhecida no mesmo formato de cabeçalho que o gateway injeta, aguardar uma quantidade fixa de trabalho e retornar um corpo e um status diferentes para credenciais boas e ruins. Ele não deve encurtar o caminho inválido.

Por exemplo, este contrato de fixture é suficiente:

```text
Request header: Authorization: Bearer test-good
Response: 200 {"fixture":"accepted"}

Request header: Authorization: Bearer test-bad
Response: 401 {"fixture":"rejected"}

Both requests: wait until the same server-side target duration has elapsed
```

A duração-alvo deve ser maior que o ruído comum do escalonamento local, mas curta o bastante para manter a suíte rápida. Escolha esse valor a partir de medições na sua própria máquina de teste, em vez de copiar um número de uma publicação. O importante é que os dois caminhos da fixture executem o mesmo caminho de análise, relógio, espera e resposta antes de diferirem semanticamente.

Para HTTP, execute a fixture em loopback quando quiser isolar o gateway e o cliente. Execute-a em um host remoto controlado em um segundo job quando quiser observar como o ruído normal da rede afeta a detecção. Mantenha esses relatórios separados. Um resultado de loopback mostra se a implementação local mudou. Um resultado remoto mostra se o teste continua sensível sob variações realistas de transporte.

Para SSH, use uma conta de teste e um comando que termine de modo conhecido depois da autenticação. Não faça o teste falhando repetidamente contra um bastion de produção. Servidores SSH podem deliberadamente desacelerar falhas, bloquear contas ou acrescentar trabalho de registro. São defesas sensatas, mas fazem sua medição contar uma história sobre o servidor, não sobre o gateway.

O Sallyport envia requisições HTTP com injeção de credenciais bearer, basic ou de cabeçalho personalizado e usa seu helper stateless `sp-ssh` integrado para SSH. Esses são canais separados e precisam de fixtures separadas. Um resultado equilibrado em um caminho de cabeçalho HTTP não diz nada sobre um caminho SSH que resolve chaves, inicia um helper e negocia uma conexão.

## Compare distribuições e depois tente classificar o estado

As médias escondem vazamentos de tempo. Se 90% das chamadas levam 12 milissegundos e 10% levam 80 milissegundos porque um cache só falha para uma credencial existente, a média pode parecer próxima o bastante de outro caso, enquanto um agente explora o grupo rápido.

Para cada caso, mantenha o conjunto completo de amostras. Informe pelo menos a mediana, os percentis inferior e superior, o mínimo, o máximo e a quantidade de amostras. Um histograma costuma revelar mais que uma tabela porque expõe imediatamente dois grupos.

Depois torne o teste adversarial. Dê a um classificador deliberadamente simples apenas a duração e peça que adivinhe o rótulo oculto. Comece com um classificador por limiar. Ele escolhe um único corte, por exemplo, «abaixo de X microssegundos significa credencial ausente», e informa a precisão em dados que não foram usados para escolher esse corte. Se um classificador de um único limiar superar repetidamente a linha de base ingênua por uma margem ampla, você tem um sinal observável que merece investigação.

Um pequeno script de análise em Python pode tornar essa regressão visível sem fingir que prova tempo constante criptográfico:

```python
from statistics import median

samples = {
    "vault_locked": [...],
    "credential_missing": [...],
}

for name, values in samples.items():
    ordered = sorted(values)
    p10 = ordered[int(len(ordered) * 0.10)]
    p90 = ordered[int(len(ordered) * 0.90)]
    print(name, {"n": len(values), "p10": p10,
                 "median": median(values), "p90": p90})

best = None
all_values = sorted(set(samples["vault_locked"] + samples["credential_missing"]))
for cutoff in all_values:
    correct = 0
    total = 0
    for label, values in samples.items():
        for value in values:
            guess = "vault_locked" if value <= cutoff else "credential_missing"
            correct += (guess == label)
            total += 1
    score = correct / total
    if best is None or score > best[0]:
        best = (score, cutoff)

print({"best_training_accuracy": best[0], "cutoff_us": best[1]})
```

Separe as amostras em grupos de treinamento e retenção antes de escolher o corte. Caso contrário, o script vai se ajustar demais ao ruído e se parabenizar. Se adicionar um classificador mais rico, mantenha-o como diagnóstico secundário. Um modelo complicado pode encontrar padrões minúsculos que nenhum agente prático consegue usar, enquanto um limiar simples expõe os vazamentos constrangedores que os engenheiros realmente introduzem.

Não defina uma condição universal de aprovação como «todas as medianas devem ficar dentro de cinco milissegundos». Esse limite não tem significado entre máquinas e configurações de teste. Use uma linha de base registrada no seu ambiente controlado, examine se as distribuições se sobrepõem como esperado e faça o teste falhar quando uma alteração criar uma separação estável entre estados que você pretendia esconder.

## A comparação em tempo constante resolve um problema menor que este

Desenvolvedores costumam recorrer a uma função de igualdade em tempo constante quando ouvem «ataque de tempo». Essa função importa para comparar segredos, assinaturas, MACs e tokens de mesmo comprimento. Ela não torna constante no tempo o caminho completo de uma requisição do gateway de ações.

O pacote `crypto/subtle` do Go fornece `ConstantTimeCompare` para slices de bytes com conteúdos de mesmo comprimento. É o tipo certo de primitiva quando a própria comparação secreta precisa de comportamento independente dos dados. Mas ela não consegue igualar um ramo que retorna antes de abrir o cofre, um ramo que renderiza um cartão de aprovação ou um ramo que abre uma conexão de rede.

Use comparação em tempo constante onde comparar valores sensíveis e de formato fixo. Depois examine o fluxo de controle ao redor. Uma comparação limpa dentro de um caminho que retorna imediatamente quando falta uma credencial ainda deixa um oráculo sobre a presença da credencial.

Por isso, também evite chamar todo o problema de «tempo constante». Para um gateway de desktop capaz de fazer chamadas HTTP e SSH, o tempo constante de ponta a ponta não é alcançável nem necessário. O requisito é mais restrito: estados locais sensíveis não devem criar um sinal de tempo convenientemente classificável para o chamador.

Os exemplos de CWE-208 da MITRE incluem saídas antecipadas durante verificações de senha e trabalhos diferentes para contas existentes e inexistentes. O padrão é o mesmo mesmo quando não há comparação de senha. A diferença observável vem do caminho de decisão, não de um único operador de igualdade inseguro.

## Os fluxos de aprovação precisam de um limite de medição explícito

A aprovação por sessão e a aprovação por chamada introduzem uma pessoa na linha do tempo. A duração de uma chamada inteira inclui o tempo que alguém leva para notar um cartão, ler a identidade do processo, decidir, autenticar com Touch ID e clicar. Essa duração varia por motivos sem relação com o estado do cofre.

Não tente escondê-la com preenchimento. Você vai piorar a interação e ainda não fará com que as pessoas se comportem de acordo com um relógio.

Em vez disso, divida o fluxo em partes automáticas e controladas por humanos. A parte automática começa quando o agente envia a chamada e termina quando o gateway produz uma recusa sem exibir um aviso ou apresenta uma solicitação de aprovação. Meça essa parte entre os estados. A parte controlada por humanos começa quando o pedido é apresentado e termina com aprovação, rejeição, timeout ou encerramento do processo. Registre-a para operações, mas não a use como alvo de equivalência de canal lateral de tempo.

Você também precisa de um modo de teste que estabeleça um estado de aprovação estável antes de coletar chamadas enviadas. Caso contrário, a distribuição da sua «credencial válida» inclui um cartão de aprovação uma vez e o ignora no restante da sessão. Isso produz um outlier enorme na primeira amostra e esconde diferenças mais sutis.

Uma boa suíte tem casos distintos para estas perguntas:

- Um cofre bloqueado recusa antes de qualquer consulta a credenciais dependente de aprovação?
- Um processo não aprovado recebe o mesmo tratamento antes do aviso, independentemente de a ação ter uma credencial configurada?
- Depois que uma sessão é aprovada, credenciais de teste válidas e inválidas percorrem caminhos comparáveis do gateway até o upstream controlado?
- Uma rejeição de aprovação por chamada expõe trabalho adicional dependente da credencial antes do aviso?

O Sallyport identifica um novo processo de agente por sua autoridade de assinatura de código no cartão de aprovação, e sua autorização por sessão dura apenas enquanto o processo estiver em execução. A suíte de tempo deve criar deliberadamente processos novos de agente ao testar o comportamento da primeira chamada e manter um processo conhecido ativo ao testar uma sessão aprovada. Misturar esses modos torna o resultado impossível de interpretar.

## O trabalho de auditoria não deve criar um caminho rápido dependente de segredos

O registro de auditoria costuma causar o último vazamento de tempo porque é tratado como atividade administrativa, não como parte do limite de segurança. Uma requisição recusada pode registrar apenas um evento breve. Uma requisição bem-sucedida pode alocar um registro detalhado, aplicar hash para formar uma cadeia e gravá-lo em armazenamento criptografado. Essa diferença pode ser legítima se o sucesso já for observável. Ela é perigosa quando duas recusas locais revelam quantidades diferentes de estado oculto.

Decida quais campos cada resultado pode registrar com segurança e faça as falhas locais equivalentes executarem trabalho de auditoria equivalente. Você não precisa escrever credenciais fictícias nem enviar requisições falsas. Mas precisa evitar um ramo em que «credencial ausente» ignore o diário enquanto «cofre bloqueado» faz uma consulta e grava um registro mais pesados, caso o agente não deva distingui-los.

Mantenha a verificação da auditoria fora do caminho da requisição. A verificação é uma ação do operador, com outra finalidade e outro perfil de tempo. A cadeia de auditoria do Sallyport pode ser verificada offline sobre o texto cifrado com `sp audit verify`, sem uma chave do cofre. Esse design impede que a verificação exija uma leitura secreta, mas não elimina a necessidade de testar o comportamento no momento da requisição em torno do registro.

Adicione spans internos em torno da análise da requisição, autorização, bloqueio do cofre, resolução da credencial, acréscimo à auditoria, envio e mapeamento da resposta. Não exponha esses spans ao agente. Quando o teste de ponta a ponta encontrar uma separação, compare as durações agregadas dos spans entre os casos para localizar a etapa que divergiu.

O erro é adicionar tracing detalhado somente depois que aparece uma regressão de tempo. Adicione a instrumentação cedo, proteja-a com uma configuração de teste ou de diagnóstico local e garanta que o caminho normal não emita logs síncronos cujo custo dependa da credencial ou do resultado.

## Coloque os testes diferenciais ao lado do código que adiciona ramificações

Uma suíte de tempo pertence à mesma revisão de alteração que os testes de autorização. As ramificações com maior probabilidade de regressão vêm de mudanças comuns de manutenção: um atalho para configuração ausente, um cache de aliases de credenciais, um novo campo de auditoria, uma mensagem de erro melhor ou uma refatoração que move a consulta ao cofre para antes da aprovação do processo.

Execute uma suíte local compacta a cada alteração que afete acesso ao cofre, autorização, resolução da ação, mapeamento de erros, configuração do transporte ou gravações de auditoria. Ela pode usar fixtures em loopback e uma quantidade moderada de amostras. Antes dos lançamentos, execute uma suíte aleatória mais longa em um ambiente controlado e silencioso, registrando separadamente os modos frio e aquecido.

Faça o teste produzir algo que um revisor possa usar. Isto é melhor que um indicador verde com uma pontuação agregada sem explicação:

```text
case pair: vault_locked vs credential_missing
samples: 400 per case, randomized order
median: 12.7 ms vs 13.1 ms
p10-p90: 11.8-14.0 ms vs 11.9-14.3 ms
holdout threshold accuracy: 51.4%
result: within baseline
```

E isto é melhor que esconder o problema sob uma falha genérica de desempenho:

```text
case pair: vault_locked vs credential_missing
samples: 400 per case, randomized order
median: 4.2 ms vs 19.6 ms
p10-p90: 3.9-4.7 ms vs 18.2-22.1 ms
holdout threshold accuracy: 99.1%
result: investigate credential lookup before authorization
```

O segundo relatório não afirma que um invasor sempre terá 99,1% de precisão em uma estação de trabalho ocupada. Ele informa ao engenheiro que o código local criou uma separação clara e repetível. Isso basta para bloquear a alteração até que a ordem das ramificações ou o envelope de resposta seja corrigido.

Não esconda o resultado com um atraso arbitrário. Mova o trabalho dependente de segredos para depois do bloqueio correto, elimine trabalhos específicos de estados que não sejam necessários e teste novamente a partir do limite do agente. O resultado útil não é um gráfico perfeitamente plano. É um agente que não consegue transformar um cronômetro em um inventário do que existe atrás do cofre.
