# Falhas de codificação de URL: teste as entradas da API do agente antes das chamadas

Uma URL não é uma string inofensiva. Ela é uma instrução estruturada, cujos delimitadores definem o host, a rota, os nomes dos parâmetros e os valores. Se um agente combinar texto fornecido pelo usuário com uma URL de forma incorreta, poderá chamar um endpoint diferente daquele revisado pelo desenvolvedor.

Já vi equipes aprovarem uma solicitação que parecia `GET /records/alice` no registro da ferramenta e depois passarem a tarde descobrindo que o servidor recebeu uma rota com uma barra extra, um parâmetro de consulta duplicado ou uma sequência de travessia decodificada. O agente não precisou de um exploit exótico. Bastou um texto comum como `a+b`, `%2F`, `\u0026admin=true` ou um nome em um sistema de escrita não ASCII.

Falhas de codificação de URL são fáceis de ignorar porque um cliente HTTP muitas vezes «cuida da codificação». Ele cuida de parte da serialização, conforme sua própria API e seus padrões. Não pode decidir se um valor do usuário pertence a um segmento de caminho, a um valor de consulta, ao corpo do formulário ou a lugar nenhum. Essa decisão deve estar na definição da ação e nos testes.

## O destino da solicitação, não a string visível, define a chamada

Uma solicitação pode mudar de significado em cada ponto em que o software analisa ou reconstrói sua URL. A sequência de caracteres produzida pelo agente é apenas o começo. A biblioteca do cliente pode normalizá-la, um proxy reverso pode reescrevê-la e o framework da aplicação pode decodificá-la antes de combinar a rota ou vincular os parâmetros.

A RFC 3986 divide uma URI em componentes: esquema, autoridade, caminho, consulta e fragmento. Dentro de um caminho, `/` é um delimitador. Dentro de uma consulta, `\u0026` e `=` ganham significado nas convenções mais comuns, embora a RFC 3986 não defina uma gramática universal para consultas. Essa distinção explica a maioria das falhas que as pessoas chamam, de forma imprecisa, de «problemas de codificação».

Considere uma ação destinada a buscar um projeto:

```text
GET https://api.example.test/projects/{project_id}
```

Se `project_id` for `north/ops`, estes dois destinos não são equivalentes:

```text
/projects/north/ops
/projects/north%2Fops
```

O primeiro tem dois segmentos de caminho depois de `projects`. O segundo tenta transportar uma barra literal dentro de um único segmento. A preservação do segundo depende de todo o caminho entre o cliente e a aplicação. Algumas pilhas decodificam `%2F` antes da combinação da rota e o transformam na primeira forma. Outras o rejeitam. Um teste que apenas afirma «a URL foi codificada» prova muito pouco.

A mesma armadilha aparece nas consultas. Uma ação que pretende pesquisar uma frase literal não deve montar isto:

```text
/search?q=USER_TEXT
```

substituindo `USER_TEXT` por texto bruto. Com `red\u0026limit=500`, o destino final pode se tornar:

```text
/search?q=red\u0026limit=500
```

A aplicação agora vê dois parâmetros de consulta. Se o código criar um objeto de consulta e fornecer `red\u0026limit=500` como valor de `q`, deverá produzir:

```text
/search?q=red%26limit%3D500
```

Essa é a fronteira concreta que deve ser testada: campo semântico na entrada, destino exato da solicitação na saída e campo semântico analisado no destino.

Não teste apenas com letras e números. Esses valores escondem justamente os problemas que os agentes revelam. Um agente recebe chamados de suporte, títulos de problemas, nomes de branches, caminhos de arquivos, URLs copiadas e texto comum. A entrada real contém delimitadores.

## Um parâmetro de caminho é um segmento, não uma URL inacabada

Trate um parâmetro de caminho como um único segmento, salvo quando o contrato da API disser explicitamente que ele aceita um caminho. Essa regra simples elimina uma quantidade surpreendente de ambiguidades.

Desenvolvedores costumam concatenar caminhos porque o resultado parece claro:

```javascript
const target = base + "/projects/" + projectId + "/builds";
```

Esse código não atribui um significado específico de componente a `projectId`. Se o valor contiver `/`, `?`, `#` ou `%`, o resultado dependerá do que o código posterior fizer com `target`. Ele também convida a um segundo erro: alguém vê um valor codificado no log, chama `encodeURIComponent` outra vez «por segurança» e produz um identificador diferente.

Monte os segmentos como dados, codifique cada segmento uma vez e una apenas os separadores pertencentes à rota. Em JavaScript, este pequeno auxiliar deixa o contrato explícito:

```javascript
function pathSegment(value) {
  if (typeof value !== "string" || value.length === 0) {
    throw new Error("project id must be a nonempty string");
  }
  return encodeURIComponent(value);
}

const path = "/projects/" + pathSegment(projectId) + "/builds";
```

`encodeURIComponent` é apropriado aqui porque codifica `/`, `?`, `#`, `\u0026` e `=`, que de outro modo alterariam o caminho ou iniciariam uma consulta ou um fragmento. Ele deixa sem escape um pequeno conjunto definido pela RFC 3986, incluindo apóstrofos e parênteses. Isso normalmente não altera a estrutura do caminho, mas um contrato rigoroso de API pode exigir um codificador mais restritivo. Decida com base na especificação da API, não no hábito.

Não use `encodeURI` para um único segmento. Ele preserva os delimitadores de URI porque espera receber uma URI completa. Se você passar `north/ops` por ele, a barra permanecerá e a rota mudará. É uma recomendação comum porque o nome da função parece correto. O escopo dela é que está errado.

Um caminho pode conter legitimamente vários segmentos, por exemplo quando uma API define `/{owner}/{repository}`. Modele isso como dois campos, não como uma string livre `resourcePath`. Se o endpoint realmente precisar de um identificador opaco que possa conter barras, considere colocá-lo em um parâmetro de consulta ou no corpo JSON da solicitação. APIs que forçam texto opaco pelas camadas de roteamento fazem todos dependerem de suposições sobre barras codificadas.

Há também uma decisão de roteamento que a codificação do cliente não consegue corrigir. Muitos proxies e frameworks normalizam segmentos de ponto, como `.` e `..`, condensam barras repetidas ou rejeitam separadores codificados. Faça uma pergunta direta ao responsável pelo endpoint: a combinação da rota ocorre antes ou depois da decodificação percentual? Depois, teste a rota implantada, incluindo o proxy. A documentação de um framework executado sozinho na máquina do desenvolvedor não responde a isso.

## Strings de consulta precisam de uma gramática definida

Uma string de consulta não é um único bloco escapado. Ela é uma coleção de campos cuja gramática pertence à API. Antes que um agente possa chamar o endpoint com segurança, é preciso decidir como lidar com nomes repetidos, valores vazios, arrays, booleanos, espaços e duplicidades.

O padrão WHATWG URL e as APIs voltadas para navegadores usam a serialização de consultas no estilo de formulário em `URLSearchParams`. Nessa convenção, um espaço costuma virar `+`, e um sinal de mais literal vira `%2B`. Muitos analisadores de servidor usam a mesma convenção. A RFC 3986, por si só, não diz que `+` significa espaço em uma URI genérica. Os dois fatos importam quando um componente usa um analisador genérico e outro usa um analisador de formulário.

Use um construtor de consultas, não templates de strings:

```javascript
const query = new URLSearchParams();
query.set("q", userText);
query.set("include_archived", "false");
for (const label of labels) query.append("label", label);

const url = "https://api.example.test/search?" + query.toString();
```

Para `userText = "C++ \u0026 systems"`, a grafia exata pode ser `q=C%2B%2B+%26+systems`. Um servidor que aplica decodificação de formulário deverá recuperar `C++ \u0026 systems`. Seu teste de regressão deve esperar o valor semântico da API, sem exigir que toda biblioteca use `%20` para espaços. `%20` e `+` podem representar um espaço nas convenções de consulta encontradas na prática; um sinal de mais literal precisa continuar sendo um sinal de mais depois da análise.

Campos repetidos exigem uma decisão explícita. Estes são contratos diferentes:

```text
?label=bug\u0026label=security
?label=bug,security
?label=["bug","security"]
```

O primeiro é um nome repetido. O segundo é um único valor contendo uma vírgula, salvo indicação contrária da API. O terceiro é uma string com aparência de JSON, não JSON, a menos que o servidor a analise deliberadamente. Não diga ao agente apenas «passe os labels na URL» e deixe o formato implícito. Dê à ação um argumento array e faça com que ela o serialize na única forma aceita pelo endpoint.

Parâmetros escalares duplicados são outra falha silenciosa. Uma solicitação com `?role=user\u0026role=admin` pode resultar no primeiro valor, no último, em um array ou em erro, conforme o framework. Uma verificação de segurança que lê o primeiro valor enquanto um serviço posterior lê o último cria uma decisão dividida. Rejeite duplicidades em campos que deveriam aparecer uma única vez, no primeiro componente sob seu controle.

Vale mencionar os fragmentos porque eles confundem a depuração. Em geral, `#section` nunca sai do cliente como parte de uma solicitação HTTP. Se a entrada bruta do usuário acrescentar `#`, ela poderá remover tudo depois dele de um objeto URL antes do envio. Codifique-o dentro de um valor de caminho ou consulta quando ele for dado. Não dependa do log da string original para saber o que chegou ao servidor.

## Sinais de porcentagem e a ordem de decodificação criam identificadores diferentes

Decodifique escapes percentuais uma vez, em uma fronteira definida. Se dois componentes decodificarem a mesma entrada, um valor que parecia inofensivo poderá virar um delimitador depois de passar pela primeira verificação.

Considere o texto `%252F`. Uma decodificação percentual produz `%2F`. Uma segunda produz `/`. Isso importa quando a validação acontece entre essas duas operações. Um gateway pode rejeitar `/` bruto em um identificador e permitir `%252F`; depois, uma aplicação anterior pode decodificar novamente e dividir a rota. O mesmo padrão vale para `%252e`, que vira `%2e` e depois `.`.

Distinga três valores tanto nas discussões de projeto quanto nos testes:

1. A grafia bruta transmitida, como `%252F`.
2. O valor depois de uma decodificação percentual, como `%2F`.
3. O valor da aplicação depois de todos os analisadores e reescritas, como `/`.

As equipes costumam chamar os três de «a URL». Essa linguagem imprecisa causa revisões ruins porque pessoas comparam etapas diferentes sem perceber.

A RFC 3986 recomenda que os produtores de URI não codifiquem nem decodifiquem a mesma string mais de uma vez. É um bom conselho, mas não é um plano de implementação. Defina onde o serializador da ação aceita strings da aplicação já decodificadas e onde o servidor aceita os bytes brutos da solicitação. Tudo entre essas fronteiras deve preservar os escapes ou rejeitar formas que não consiga preservar.

Não aceite entradas já codificadas por conveniência. Um prompt de agente que diga «forneça um ID de projeto codificado na URL» pede ao modelo que adivinhe se `%2F` é dado ou instrução. A próxima camada não saberá se deve preservar o sinal de porcentagem ou codificá-lo como `%25`. Aceite campos de texto simples, codifique-os uma vez na camada de ações e rejeite escapes percentuais inválidos apenas quando URLs brutas forem aceitas de propósito.

Unicode acrescenta outra fronteira. Um cliente de URL normalmente transforma texto em bytes UTF-8 e codifica em porcentagem os bytes que não podem aparecer diretamente no componente escolhido. Frameworks de servidor podem normalizar Unicode de maneiras diferentes antes de procurar um usuário ou recurso. Mantenha identificadores em uma forma canônica na camada da aplicação quando o domínio exigir isso. Não tente resolver a identidade Unicode com escapes de URL. Escapes transportam bytes; não decidem se duas strings visualmente semelhantes nomeiam a mesma conta.

## Teste toda a rota com entradas adversárias, mas comuns

Um teste útil de codificação observa duas coisas: o destino da solicitação emitido pelo cliente e os valores analisados pelo receptor. Se você testar apenas um dos lados, uma reescrita de proxy ou um decodificador de framework ainda poderá alterar o significado no meio do caminho.

Comece com um handler de eco controlado no seu ambiente de testes. Ele deve registrar o destino bruto da solicitação quando o runtime do servidor o expuser e devolver o caminho e os campos de consulta analisados. Não coloque credenciais nesse endpoint. A função dele é expor a serialização, não autenticar ninguém.

Este pequeno handler Node mostra o formato da resposta. Ele devolve deliberadamente uma propriedade de solicitação com aparência bruta e os valores analisados, porque esses campos identificam defeitos diferentes:

```javascript
import http from "node:http";

http.createServer((req, res) => {
  const url = new URL(req.url, "http://local.test");
  const pairs = [...url.searchParams.entries()];
  res.setHeader("content-type", "application/json");
  res.end(JSON.stringify({
    requestTarget: req.url,
    pathname: url.pathname,
    queryPairs: pairs
  }, null, 2));
}).listen(8787);
```

Envie casos conhecidos e mantenha a saída esperada. Por exemplo, um construtor de consultas que receba `C++ \u0026 systems` deverá produzir uma saída com um único par `q`, cujo valor analisado seja exatamente `C++ \u0026 systems`. Uma consulta montada manualmente costuma se revelar ao devolver dois pares ou ao transformar os sinais de mais em espaços.

```text
{
  "requestTarget": "/search?q=C%2B%2B+%26+systems",
  "pathname": "/search",
  "queryPairs": [["q", "C++ \u0026 systems"]]
}
```

Sua suíte deve cobrir uma matriz compacta, não dezenas de strings aleatórias:

- Espaço, sinal de mais literal, sinal de porcentagem, e comercial, sinal de igualdade, ponto de interrogação e cerquilha.
- Barra e barra codificada dentro de um identificador destinado a ser um único segmento.
- Strings vazias, campos opcionais ausentes e nomes de consulta repetidos.
- Um valor Unicode e um valor cujos escapes percentuais se parecem com uma segunda passagem de decodificação.
- Uma URL completa colada em um campo que espera um identificador.

Use testes baseados em propriedades se essa prática já fizer parte da equipe, mas não esconda os casos nomeados atrás de exemplos gerados. Eles documentam por que a fronteira existe. Quando surgir uma regressão, `encoded slash stays inside project_id` será muito mais útil que um número de seed.

Execute os mesmos testes de integração pelo caminho usado pelo tráfego de produção. Um teste direto contra o processo da aplicação não mostrará se o proxy rejeita `%2F`, reescreve barras duplicadas ou escolhe outro valor de um parâmetro duplicado. Se a borda de produção não puder ser incluída nos testes locais, use uma rota de homologação com a mesma configuração e torne esse teste parte da verificação de lançamento.

## Dê argumentos estruturados aos agentes, não liberdade para construir URLs

Um agente deve escolher uma ação e fornecer argumentos tipados. A implementação da ação deve escolher o método HTTP, a origem permitida, o template da rota, a gramática da consulta, os cabeçalhos e a codificação. Permitir que o agente entregue uma URL completa reúne todos esses controles em uma única string ambígua.

Um contrato restrito de ação poderia ser assim:

```json
{
  "name": "get_project_builds",
  "input": {
    "project_id": "north/ops",
    "branch": "release+candidate",
    "limit": 25
  }
}
```

O código da ação deve validar `project_id` como identificador, codificá-lo como um único segmento, colocar `branch` em um construtor de consultas, validar `limit` como um inteiro dentro do intervalo permitido pela API e construir a URL a partir de uma origem fixa. O modelo nunca precisa ver um bearer token nem decidir onde um e comercial deve ficar.

Essa separação também evita confusão de origem. Uma string que começa com `https://other.example` não pertence a um campo de identificador. Se uma ação realmente buscar uma URL fornecida pelo usuário, faça dela uma ação distinta, com uma lista de permissões documentada, regras claras para DNS e redirecionamentos e uma justificativa explícita. Não esconda uma capacidade de busca arbitrária em um campo chamado `callback` ou `file`.

Tenha cuidado com APIs que aceitam linguagens de filtro nos parâmetros de consulta. Um campo como `filter=status:open AND owner:me` tem duas gramáticas: a serialização da consulta da URL e a própria linguagem de filtro. A codificação de URL mantém o filtro em um único valor de consulta. Ela não torna o filtro seguro. Analise ou restrinja essa linguagem interna separadamente, ou ofereça campos de filtro tipados.

Evite colocar segredos em URLs. Strings de consulta acabam em logs de acesso, histórico do navegador em alguns contextos, telemetria e relatórios de erro. As credenciais pertencem ao mecanismo de autorização esperado pela API. Codificar um token de API não torna segura sua presença em uma consulta.

## A aprovação é útil, mas não corrige uma solicitação malformada

A autorização humana e a serialização correta resolvem problemas diferentes. Uma aprovação pode confirmar que um processo de agente reconhecido pode usar uma credencial. Ela não revela se uma sequência percentual virará uma barra em um roteador posterior ou se um parâmetro duplicado alterará privilégios depois da análise.

Essa distinção importa quando alguém afirma que prompts ou cartões de aprovação tornam desnecessária a construção rigorosa de solicitações. Não tornam. Uma pessoa que revisa `https://api.example.test/projects/%252Fadmin` teria de simular mentalmente cada decodificador do caminho para saber o que acontecerá. Isso não é um controle de segurança razoável, especialmente quando um agente pode fazer muitas chamadas em uma sessão.

Coloque verificações determinísticas antes que a chamada chegue à etapa de aprovação:

- Aceite campos estruturados em vez de um destino de solicitação pré-montado.
- Fixe a origem, o método e o template da rota na definição da ação.
- Serialize cada segmento de caminho e valor de consulta uma única vez.
- Rejeite campos duplicados ou malformados que o endpoint não define.
- Teste o destino final pelo caminho de solicitação implantado.

O Sallyport pode manter as credenciais fora de um agente compatível com MCP e exigir autorização para um processo de agente ou para cada uso de uma credencial selecionada. Esse controle humano funciona melhor quando a camada de ações já produz uma solicitação sem ambiguidades.

Os logs também precisam dos dois níveis de detalhe. Registre a ação aprovada e seus argumentos seguros e depois mantenha uma representação redigida do destino real da solicitação e do resultado HTTP. Se um serviço tiver um campo de consulta sensível, redija seu valor, mas mantenha o nome do parâmetro e informações suficientes para diagnosticar duplicações acidentais. Nunca registre cabeçalhos de autorização só porque uma solicitação falhou.

## A normalização da rota pode derrotar um cliente correto

Uma solicitação perfeitamente serializada pelo cliente ainda pode mudar nas fronteiras da infraestrutura. Proxies, balanceadores, firewalls de aplicações web e servidores de aplicação têm opiniões diferentes sobre barras duplicadas, segmentos de ponto, separadores codificados e escapes inválidos. Você precisa de um contrato de rota para toda a cadeia.

Considere um endpoint cujo cliente envia este caminho:

```text
/files/reports%2F2025%2Fnotes
```

Se a API esperar um único ID de arquivo opaco, o parâmetro desejado da aplicação será `reports/2025/notes`. Porém, um proxy que decodifica antes de encaminhar pode enviar `/files/reports/2025/notes`. Um roteador configurado para `/files/:id` pode rejeitá-lo. Outro pode combinar `/files/:folder/:year/:name` e chamar um handler diferente. Nenhum desses resultados prova que o codificador do cliente falhou.

Tome uma decisão para cada rota sensível. Você pode rejeitar barras codificadas na borda e documentar que os IDs não podem conter barras. Pode preservá-las até o handler e testar essa propriedade. Ou pode redesenhar o endpoint para que os dados opacos fiquem fora do caminho. O que não deve fazer é deixar o comportamento depender de padrões específicos de uma versão e chamar isso de limite de segurança.

Observe também os redirecionamentos. Clientes HTTP podem segui-los automaticamente, e uma URL redirecionada pode carregar um caminho ou uma consulta normalizados de maneira diferente. Para ações com credenciais, defina se redirecionamentos são permitidos, se a origem de destino deve ser a mesma e se os cabeçalhos de autorização devem ser removidos quando a origem mudar. Isso é separado da codificação percentual, mas problemas de URL e regras de redirecionamento frequentemente se encontram no mesmo código de solicitação.

Se você opera vários serviços, teste deliberadamente as divergências. Envie o mesmo caminho codificado pela borda e diretamente à aplicação em um ambiente que não seja de produção. Se os valores de caminho analisados forem diferentes, corrija isso antes de conceder ao agente acesso à rota. Um analisador inconsistente é um problema operacional mesmo que nenhum invasor o explore.

## Uma suíte de regressão deve preservar o significado pretendido

O objetivo dos testes de URL não é impor uma grafia de escape preferida. É preservar a relação entre os argumentos da ação e o significado da solicitação no servidor à medida que bibliotecas e infraestrutura mudam.

Escreva asserções em três camadas. Os testes unitários devem verificar que um codificador de segmentos transforma `a/b` em um único segmento codificado e que a serialização de consultas preserva `\u0026` dentro de um valor. Os testes da ação devem capturar o método, a origem, o caminho e os pares de consulta exatos enviados a um handler de eco. Os testes de integração devem passar pela borda semelhante à produção e confirmar que o handler recebe a rota e os parâmetros esperados.

Quando um contrato de API for vago, registre a ambiguidade e elimine-a. «Aceita texto de pesquisa na URL» não é um contrato. Declare se `q` vazio é aceito, se `q=a+b` significa mais ou espaço, se valores repetidos de `tag` são permitidos e se `%2F` é permitido em IDs. Esses detalhes deixam de ser acidentais quando um agente pode gerar chamadas a partir de texto humano arbitrário.

Não esconda um teste que falha decodificando a entrada mais cedo. A decodificação antecipada muitas vezes faz o caso parecer normal, mas desloca a ambiguidade para uma camada menos visível. Mantenha o texto bruto do usuário como dado até que o serializador específico do componente o trate. Depois, examine o que o receptor realmente analisou.

O diário de atividades do Sallyport pode facilitar a comparação entre a chamada aprovada por um agente e o resultado devolvido, enquanto sua cadeia de auditoria pode ser verificada offline com `sp audit verify`. Use essa trilha para investigar uma divergência, não como substituta da decisão sobre o que o endpoint aceita.

O primeiro teste que eu adicionaria é quase banal: um identificador contendo `/`, um valor de consulta contendo `+` e `\u0026` e um sinal de porcentagem que deve permanecer literal. Se sua ação não consegue declarar exatamente o que o servidor recebe para essas entradas, ela ainda não está pronta para aceitar texto fornecido por usuários através de um agente.
