A redação do cartão de aprovação precisa preservar a decisão
A redação de cartões de aprovação deve ocultar credenciais e dados privados, mantendo destino, alvo, efeito e parâmetros arriscados claros o bastante para uma aprovação segura.

Um cartão de aprovação tem uma única função: ajudar uma pessoa a decidir se uma ação específica pode sair da máquina. Se o cartão oculta detalhes suficientes para proteger uma credencial, mas também esconde o que a solicitação fará, ele falhou nessa função.
O erro mais comum é tratar a redação como substituição de strings. Engenheiros mascaram Authorization, deixam o corpo JSON em branco e consideram o resultado seguro. O operador então vê POST https://api.example.com/... e um botão Aprovar. Isso não é consentimento informado. É um ritual vazio que condiciona as pessoas a clicar justamente no momento em que o controle humano deveria importar.
Um bom cartão preserva o significado da ação e remove o material que permitiria a quem lê, fotografa, registra ou observa o cartão por cima do ombro reutilizar um segredo. Isso exige uma renderização consciente dos campos. Cabeçalhos, strings de consulta, corpos e identificadores de destino precisam de tratamentos diferentes.
Um cartão de aprovação precisa explicar a ação
Em poucos segundos, o operador deve conseguir responder a quatro perguntas: quem está solicitando, para onde a solicitação vai, o que ela fará e qual objeto ou escopo será afetado. Se qualquer resposta estiver ausente, o cartão está incompleto, mesmo que todos os segredos estejam perfeitamente ocultos.
Comece com uma linha de ação que combine o método do protocolo com um verbo compreensível:
POST api.billing.example /v1/invoices/inv_7KD2/refund
Action: issue a refund
O método importa porque GET, POST, PATCH e DELETE criam expectativas diferentes. O verbo compreensível importa porque o método sozinho não informa ao operador se POST /v1/invoices/inv_7KD2/refund cria um rascunho, envia um pagamento ou dispara um reembolso. Não peça à pessoa que deduza a semântica da aplicação a partir do nome da rota quando quem chama já conhece a operação pretendida.
Depois, mostre o destino como uma autoridade real, não como um apelido de conta. «Produção de faturamento» pode ser um contexto adicional útil, mas não substitui api.billing.example. Uma solicitação redirecionada pode usar um rótulo familiar. O host é o limite que informa qual serviço receberá os dados e a credencial.
A RFC 3986 separa uma URI em componentes, incluindo autoridade, caminho, consulta e fragmento. Essa divisão é útil para renderizar aprovações porque cada componente traz um tipo diferente de sinal para a decisão. Não reduza tudo a uma única string de URL bonita esperando que uma máscara posterior preserve as informações certas.
O cartão também deve dizer se a ação cria, altera, exclui, publica, transfere ou apenas lê. «Alterar registro de cliente» é mais fraco que «alterar o destino de pagamento do cliente». Se o construtor da solicitação não consegue fornecer essa frase, corrija o construtor. Um renderizador de cartões não consegue reconstruir com segurança a intenção comercial a partir de um JSON arbitrário.
Renderize um manifesto da solicitação, não uma solicitação embelezada
A unidade segura de trabalho é um manifesto de solicitação tipado. Ele registra o que o agente pretende fazer antes da injeção de credenciais e antes de existir qualquer visualização de aprovação.
Um manifesto mínimo pode ter esta aparência:
{
"channel": "http",
"method": "POST",
"destination": {
"scheme": "https",
"host": "api.billing.example",
"port": 443,
"path_template": "/v1/invoices/{invoice}/refund"
},
"action": "issue refund",
"targets": [
{"role": "invoice", "display": "inv_7KD2", "sensitivity": "internal"}
],
"query": [],
"headers": [],
"body": {
"media_type": "application/json",
"fields": []
},
"effect": "financial"
}
Isso não é uma representação HTTP pronta para transmissão. É o objeto que o renderizador de aprovação deve consumir. A diferença importa. Uma solicitação transmitida inclui credenciais injetadas, valores codificados e detalhes de transporte. Um manifesto traz rótulos semânticos, como action, target role e effect, que uma solicitação bruta não possui.
Classifique cada valor exibível pelo que o operador precisa decidir, não pelo lugar onde ele apareceu. Um token bearer em um cabeçalho é secreto. Uma URL de webhook assinada em uma string de consulta também é secreta. Um endereço de e-mail dentro de um corpo JSON pode ser um dado pessoal. O nome de um repositório pode ser um identificador de destino cuja visibilidade é necessária para uma decisão segura.
Use um vocabulário pequeno e consistente:
public: seguro para mostrar como está.internal: mostre quando identifica o objeto afetado, mas evite copiá-lo para logs amplos.personal: mostre apenas a forma mínima útil, normalmente um rótulo acompanhado de parte do valor.secret: nunca mostre o valor no cartão, nos logs, na área de transferência ou no texto de erro.opaque: mostre um alias aprovado ou apenas uma referência estável que não seja secreta quando isso ajudar a distinguir o destino.
Não ofereça aos chamadores uma saída irrestrita safe_to_display: true. Alguém a usará para facilitar uma sessão de depuração e depois a deixará em um caminho que processa credenciais de produção. Exija uma classificação concreta no limite em que o agente constrói a ação.
Cabeçalhos precisam de nomes, finalidade e quase nunca de valores
Os nomes dos cabeçalhos frequentemente dizem muito mais ao operador que os valores. Os valores muitas vezes carregam exatamente aquilo que não deve chegar a uma superfície voltada para pessoas.
Mostre o nome de todo cabeçalho relevante para a segurança e um rótulo curto de finalidade. Por exemplo:
Headers
Authorization: bearer credential from vault
Idempotency-Key: generated request identifier
X-Request-Reason: "refund requested by finance"
Content-Type: application/json
A primeira linha informa ao operador que a solicitação será autenticada, e a origem da credencial mostra se o processo está usando um segredo armazenado esperado. Mostrar Bearer eyJ... não acrescenta valor à aprovação. Isso cria um caminho de exposição e incentiva as pessoas a comparar fragmentos de token sem significado.
A especificação de tokens bearer do OAuth define o cabeçalho de solicitação Authorization como o método de transmissão preferencial, enquanto suas orientações de segurança tratam tokens bearer como credenciais que precisam de proteção durante o trânsito e o armazenamento. Esse também é o modelo mental correto para a interface de aprovação: o usuário precisa saber que uma credencial bearer será aplicada, não inspecionar a credencial.
Use estas regras para cabeçalhos:
- Mostre valores de protocolo seguros, como
Content-Type,AccepteIf-Match, quando eles afetarem o comportamento. - Mostre os nomes, mas não os valores, de
Authorization,Proxy-Authorization,Cookie,Set-Cookie, cabeçalhos de assinatura, cabeçalhos de chave de API e cabeçalhos personalizados classificados como secretos. - Mostre um valor limitado e escapado para um contexto comercial declarado como não secreto, como
X-Request-Reason, apenas se ele for curto e não puder conter material pessoal ou secreto. - Mostre que um cabeçalho está ausente quando a ausência muda a decisão. Um
If-Matchausente pode importar em uma operação de sobrescrita. - Nunca renderize todos os cabeçalhos por padrão. Bibliotecas de solicitação acrescentam ruído, e o ruído esconde o único cabeçalho que muda a ação.
Um design ruim comum mascara um segredo com os quatro primeiros e os quatro últimos caracteres: sk_live_...9a31. Esse padrão parece cuidadoso, mas é inseguro para valores curtos, valores estruturados, chaves de teste e valores que já vazaram em outro lugar. Ele também faz as pessoas acreditarem que deveriam reconhecer fragmentos de segredos. Substitua o valor por uma declaração de tipo, como credencial de API armazenada ou assinatura da solicitação.
Os cabeçalhos também podem esconder identificadores de destino. Um cabeçalho de roteamento de locatário, um cabeçalho de personificação ou X-Account-ID pode alterar quem receberá o efeito. Não o oculte apenas por ser um cabeçalho. Mostre sua função e um rótulo de destino seguro: X-Account-ID: account "Northwind production". Se não puder associar um identificador opaco a um rótulo seguro, declare que um identificador de conta opaco será usado e exija uma aprovação mais deliberada para operações sensíveis.
Strings de consulta merecem mais desconfiança
Strings de consulta ficam visíveis em URLs, são copiadas para terminais, incorporadas a relatórios de erro e frequentemente registradas por uma infraestrutura que nunca vê o corpo da solicitação. Essa conveniência é justamente o motivo pelo qual os cartões de aprovação precisam tratá-las com cuidado.
A RFC 9110 alerta que informações em uma URI podem ser divulgadas por referências, logs e outros canais, e recomenda que os remetentes evitem informações sensíveis nas URIs de destino HTTP. Isso não é uma preocupação abstrata de padrões. Um cartão que renderiza uma string de consulta completa pode se tornar mais um canal de divulgação de um valor que nem deveria estar na URI.
Não conclua que todos os valores de consulta são seguros porque a solicitação é um GET. Use nomes, tipos declarados e o contexto da operação.
GET api.crm.example /v2/contacts
Query
status = "active"
owner = "sales-west"
include = "notes"
access_token = [secret, hidden]
search = [private text, hidden]
status e include costumam ser informações úteis para a decisão. search pode conter nomes, endereços de e-mail, termos médicos ou qualquer coisa que um agente tenha extraído de arquivos locais. access_token é obviamente secreto, mas o design não pode depender de nomes óbvios. Algumas APIs usam sig, token, key, code, state, assertion ou um parâmetro específico do fornecedor sem qualquer indicação no nome.
Trate os valores de consulta como secretos por padrão, a menos que o manifesto os tenha classificado explicitamente. Isso é deliberadamente mais rigoroso que muitos exploradores de API. Uma interface de aprovação não é um console de depuração. O leitor precisa de informação suficiente para autorizar a solicitação, não de uma reconstrução byte a byte.
Preserve parâmetros duplicados e sua ordem quando isso afetar a semântica. Um renderizador que converte uma string de consulta em um dicionário pode perder silenciosamente tag=urgent&tag=finance, transformar valores repetidos ou esconder um erro de assinatura. Mostre uma lista de entradas, não um mapa:
Query
label = "finance"
label = "urgent"
expand = "line_items"
Se um campo de consulta oculto alterar o roteamento ou a autorização da solicitação, diga isso. signature = [signed request value, hidden] oferece ao operador um sinal melhor que uma linha em branco. Se a consulta incluir um link de compartilhamento opaco, não exponha o token. Mostre o rótulo do recurso quando conhecido, como shared report: Q2 forecast, e, caso contrário, exiba shared-resource token present.
Os corpos devem manter sua forma depois da redação
Um corpo que se transforma em [redacted] quase não informa nada ao operador. Um corpo que mostra todos os campos literalmente acabará vazando algo que nunca deveria ter chegado à superfície de aprovação. A resposta correta é a redação estrutural.
Renderize o corpo como uma árvore tipada. Mantenha as chaves dos objetos, a quantidade de itens nos arrays, os tipos de dados, os valores seguros de enumeração e os rótulos de destino selecionados. Substitua folhas inseguras por um marcador explicativo.
{
"invoice": "inv_7KD2",
"amount": {"currency": "USD", "minor_units": 12500},
"reason": "duplicate charge",
"customer_note": "[private text, 84 characters]",
"payment_method": {
"id": "[opaque payment method]",
"token": "[secret, hidden]"
}
}
Essa representação permite ao operador ver que a ação reembolsa 125,00 USD por um motivo declarado e carrega uma nota privada que sairá da máquina. Isso basta para decidir se a solicitação corresponde à tarefa pretendida. O cartão não divulga a nota nem o token.
Preserve os números quando eles forem o efeito. Ocultar valores de pagamentos, quantidades de assentos, períodos de retenção, limites de taxa, níveis de permissão e quantidades de exclusões torna a aprovação sem sentido. Trate esses valores como parâmetros da ação, não como dados incidentais. Um corpo DELETE contendo {"purge": true} deve mostrar purge: true; caso contrário, o cartão esconde a parte irreversível.
O texto precisa de uma regra separada. Texto livre pode conter código-fonte, dados de clientes, segredos colados ou instruções que alteram a ação. Mostrar uma prévia arbitrária é tentador porque ajuda o operador a identificar algo absurdo. Também transforma a janela de aprovação em uma superfície de exfiltração de dados. Para texto livre não classificado, mostre o nome do campo, a quantidade de caracteres e a função do destino. Só mostre um trecho limitado quando o chamador marcar o campo como público ou interno e o renderizador escapar caracteres de controle.
Arrays precisam de quantidades e resumos. Isto é ruim:
recipients: [redacted]
Isto é melhor:
recipients: 37 email addresses [personal values hidden]
Em uma operação destrutiva, a quantidade muda a decisão. Em uma alteração de acesso, mostre a função e a quantidade: add 4 members to role: billing-admin. Se os membros forem identificadores internos que o aprovador precisa distinguir, mostre nomes de exibição ou aliases aprovados, não IDs brutos.
Nunca deduza a sensibilidade apenas pelo nome do campo. password, token e secret merecem uma lista de negação rígida, mas content, message, value, data e metadata podem conter o mesmo material. Um esquema, um construtor de ações ou uma anotação explícita do campo precisa fornecer a classificação. Um filtro baseado em nomes é a última linha de defesa, não o design principal.
Os identificadores de destino devem ser legíveis, não totalmente expostos
O destino é o objeto que dá consequência à solicitação. Ele pode estar em um segmento do caminho, em um cabeçalho, em um parâmetro de consulta, em um campo JSON ou em um argumento de comando SSH. O cartão precisa tornar o destino visível mesmo quando não puder mostrar o identificador bruto com segurança.
Separe a referência de máquina do destino de sua forma de exibição humana:
{
"role": "repository",
"raw_reference": "repo_01HZX8M9...",
"display": "payments-service",
"scope": "production",
"sensitivity": "internal"
}
A referência bruta pode ser necessária para a execução, mas a forma de exibição é o que pertence ao cartão. Se a ação alterar permissões, use uma frase que nomeie a relação: Grant deploy permission on payments-service production to the release automation account. O cartão não deve obrigar o aprovador a memorizar IDs opacos.
Às vezes, o valor bruto é o único identificador disponível. Não resolva isso mostrando tudo. Escolha uma referência estável e não reversível, como um alias local ou uma referência curta de aprovação gerada a partir do valor protegido. Não chame um identificador truncado de hash a menos que ele seja um resumo criptográfico real e você compreenda as consequências de colisão e correlação. Em muitos casos, customer record [opaque reference 4F8C] é mais honesto que fingir que o operador consegue verificar cus_Qa8J7kW2m9 de relance.
Não oculte demais identificadores que determinam o raio de impacto. Uma solicitação para DELETE /projects/{project}/members com o projeto oculto é um cartão perigoso, mesmo que todos os identificadores pessoais dos membros estejam mascarados. Mostre o nome de exibição do projeto, o ambiente e a quantidade de membros afetados. Mantenha os valores individuais sensíveis fora de vista.
Há uma distinção fundamental: ocultar um segredo protege a confidencialidade; ocultar um destino enfraquece a autorização. As equipes costumam juntar os dois sob o termo «redação». São funções diferentes, e um cartão precisa de regras diferentes para cada uma.
O escopo da aprovação deve corresponder às informações do cartão
Um cartão completo não autoriza mais do que descreve. Se a pessoa aprovou uma solicitação para ler um repositório, essa aprovação não pode abranger silenciosamente uma solicitação posterior para alterar as configurações do repositório só porque ambas vieram do mesmo processo de agente.
A aprovação por sessão e a aprovação por chamada respondem a perguntas diferentes. A aprovação por sessão responde se esse processo assinado pode agir pelo gateway durante a execução. A aprovação por chamada responde se essa ação específica de saída, com esse destino e esse efeito, pode ocorrer. Juntá-las em uma única permissão enorme faz o primeiro cartão carregar um peso impossível.
Use uma regra de escalonamento baseada nas consequências. Uma leitura de um serviço conhecido pode caber em uma autorização de sessão. Uma chamada que usa uma credencial especialmente protegida, altera acessos, envia uma mensagem, cria um compromisso financeiro ou exclui dados precisa de um cartão vinculado ao manifesto concreto da solicitação.
O resultado da aprovação deve ser vinculado a um resumo canônico da ação, não ao texto visível do cartão. O resumo deve incluir método, destino normalizado, referências de destino, parâmetros não secretos classificados e uma representação dos campos protegidos. Também deve incluir metadados suficientes para detectar uma alteração na solicitação depois da renderização. Não vincule a decisão apenas a um resumo conveniente para capturas de tela.
Por exemplo, estas duas chamadas exigem aprovações diferentes, embora um renderizador descuidado possa fazê-las parecer iguais:
POST /v1/roles/grant
body: role = "viewer", subject = "build-bot"
POST /v1/roles/grant
body: role = "owner", subject = "build-bot"
A função não é um detalhe a esconder em uma visualização JSON recolhida. Ela é a ação. Se um engenheiro disser que o cartão ficou cheio demais, remova primeiro os dados decorativos do protocolo. Não remova o campo que determina se o agente pode assumir uma conta.
O Sallyport mantém a autorização da sessão separada das chaves por chamada por esse motivo. Uma decisão de sessão pode identificar e admitir um novo processo de agente, enquanto uma credencial marcada para aprovação a cada uso continua solicitando confirmação antes de cada ação individual.
Uma falha de redação geralmente começa antes da renderização
Considere um agente solicitado a enviar um contrato para assinatura. Ele constrói esta solicitação:
POST /v1/envelopes?template=msa&signature=QmFzZTY0U2lnbmVkVmFsdWU HTTP/1.1
Host: api.signing.example
Authorization: Bearer eyJhbGciOi...
Content-Type: application/json
{
"recipients": [
{"name": "Maya Chen", "email": "[email protected]"}
],
"subject": "MSA for Northwind",
"message": "Please sign the attached agreement.",
"document": "JVBERi0xLjQK..."
}
A implementação superficial formata a solicitação bruta, substitui o valor de Authorization e trunca as linhas longas. Agora o cartão expõe a assinatura na string de consulta, o e-mail do destinatário e talvez o início de um documento codificado como texto. Truncar não é fazer redação. Apenas torna o vazamento menos previsível.
Um manifesto correto separa primeiro as partes:
POST api.signing.example /v1/envelopes
Action: send contract for signature
Target: template "msa"
Recipients: 1 email address [personal value hidden]
Subject: "MSA for Northwind"
Message: public text, 39 characters
Document: 1 PDF attachment [content hidden]
Credential: bearer credential from vault
Request signature: present, hidden
Esse cartão permite ao operador detectar um host errado, o modelo errado, uma quantidade inesperada de destinatários ou um envio acidental. Ele não expõe a credencial, a assinatura, o endereço de e-mail nem os bytes do documento.
A versão perigosa não falhou porque o padrão de máscara não encontrou signature. Ela falhou porque o sistema tratou uma solicitação HTTP como texto pronto para exibição. O renderizador recebeu um bloco contendo segredos, sem tipos de campo, funções de destino ou conhecimento sobre quais valores carregavam o significado da operação.
Construa um renderizador que negue por padrão
O renderizador deve aceitar apenas entradas estruturadas, aplicar regras de exibição permitidas e se recusar a renderizar uma ação que tenha campos de saída não classificados. Isso parece rígido porque é rígido. Um campo não classificado é uma decisão adiada, e o momento da aprovação é tarde demais para adivinhar.
Um contrato prático de renderização tem três etapas:
- Normalize a ação planejada em um manifesto antes da injeção de credenciais e da codificação para transporte.
- Valide se todo campo tem tipo, rótulo de sensibilidade e regra de exibição. Rejeite cabeçalhos, valores de consulta e folhas do corpo desconhecidos, a menos que o chamador os encaminhe explicitamente para uma representação oculta segura.
- Renderize um layout fixo de cartão que reserve espaço destacado para destino, ação, alvos, efeito e avisos sobre campos protegidos.
Não permita HTML, caracteres de controle de terminal, markdown ou controles direcionais Unicode arbitrários em valores visíveis. Faça o escape antes do layout. Um valor malicioso não deve conseguir transformar recipient: [email protected] em uma linha enganosa, criar botões falsos ou reordenar visualmente um identificador de destino.
Defina também limites de exibição. Uma string pública ainda pode ter 50.000 caracteres e tornar o cartão inutilizável. Limite o texto visível por tipo de campo, informe que ele foi encurtado e mantenha uma visualização detalhada controlada apenas para conteúdo que o manifesto declarou seguro. Nunca transforme «mostrar solicitação completa» em uma saída universal.
Teste o renderizador com exemplos hostis, não apenas com chamadas normais de API. Inclua um token bearer em todos os locais possíveis, nomes de consulta duplicados, JSON com arrays aninhados, um segmento de caminho contendo delimitadores codificados em porcentagem, um segredo vazio, um segredo curto, um campo de texto muito longo e um valor contendo quebras de linha. Teste também solicitações cujo significado prejudicial seja um booleano, uma quantidade, uma função ou um host de destino.
Por fim, registre o que a aprovação cobriu sem copiar segredos em texto simples para o registro de evidências. Os registros de atividade e sessão do Sallyport derivam de um único log de auditoria criptografado e encadeado por hash, e seu comando de verificação offline pode conferir a cadeia sem uma chave do cofre. Essa é a separação a buscar: a auditabilidade deve estabelecer o que aconteceu sem se tornar um segundo cofre cheio de credenciais reutilizáveis.
O cartão deve fazer uma ação errada parecer errada. Se uma pessoa pode aprovar uma solicitação que contém credenciais sem ver seu destino, efeito e alvo, o sistema ocultou justamente os fatos necessários para protegê-la.
FAQ
Que informações um pedido de aprovação de API deve mostrar?
Mostre o método, o host de destino, o formato significativo do caminho e os nomes dos campos enviados. Oculte credenciais, tokens de sessão, valores assinados, conteúdo privado e identificadores que revelem mais do que o operador precisa para aprovar a ação.
Um cartão de aprovação deve mostrar a URL completa?
Geralmente, não. Uma URL completa pode colocar segredos em uma string de consulta e expor identificadores privados de contas, documentos ou locatários. Mostre o host e o caminho normalizado, depois apresente separadamente os nomes dos parâmetros de consulta selecionados e seus valores seguros.
Um pedido de aprovação deve revelar os valores dos tokens de API?
Oculte completamente o valor, a menos que um prefixo ou sufixo curto mude a decisão. Para credenciais bearer, cabeçalhos assinados, cookies e chaves de API, o nome do campo costuma ser suficiente, porque o operador precisa saber que a credencial será usada, não qual é o seu valor.
Como ocultar campos JSON sensíveis em um pedido de aprovação?
Use o nome e o tipo do campo, além de fatos estruturais seguros: se ele está presente, se está vazio, seu tamanho quando isso for útil e uma classificação não reversível, como segredo ou identificador opaco. Não use uma máscara reversível que exponha o suficiente de um valor curto para permitir sua reconstrução.
É seguro mostrar parâmetros da string de consulta?
Trate os parâmetros de consulta como não confiáveis até classificá-los. Os nomes podem ajudar, mas os valores frequentemente contêm credenciais, solicitações assinadas, termos de pesquisa, endereços de e-mail, códigos de indicação ou estado da aplicação.
O que é um identificador de destino em um cartão de aprovação?
Um identificador de destino informa ao operador qual objeto será afetado, como uma organização, um repositório, um ambiente, uma fatura ou uma conta. Ele deve continuar visível quando muda a decisão de autorização, mas deve ser generalizado ou ocultado quando expõe um identificador pessoal ou secreto.
A redação torna segura a aprovação de uma solicitação perigosa?
Não. A redação protege a pessoa que lê o cartão contra a exposição de um segredo, mas não reduz o poder da solicitação. O cartão ainda precisa declarar claramente a ação, o destino, o escopo e o efeito irreversível para que a aprovação tenha significado.
Uma única aprovação pode abranger todas as solicitações feitas por um agente?
Não aprove toda a sessão por padrão se as chamadas individuais puderem variar de forma relevante. A aprovação da sessão pode estabelecer quem está executando, enquanto chamadas que gastam dinheiro, excluem dados, alteram acessos ou usam uma credencial especialmente marcada devem continuar exigindo uma decisão separada.
Como os desenvolvedores devem implementar a redação de cartões de aprovação?
Mantenha uma representação canônica interna da solicitação, com campos tipados e rótulos de sensibilidade, e gere a visualização de aprovação separadamente a partir dela. Nunca crie o cartão pegando uma string de solicitação bruta e aplicando algumas expressões regulares no final.
O que deve ser registrado em um log de auditoria após uma aprovação?
Registre o formato da ação e as referências protegidas, não os segredos em texto simples. Um revisor deve conseguir confirmar qual processo de agente fez qual solicitação, para onde ela foi, qual operação tentou realizar e se uma pessoa a aprovou, sem transformar o registro de auditoria em outro armazenamento de segredos.