quarta-feira, 4 de novembro de 2026 Edição nº 228
Buscar
Avaliação · Infraestrutura · 20 min de leitura

Injeção de Credenciais no Google AX: Hands-On com Claude Code e GitHub

Por Fabio Douek

Ir para seção
Explica (TLDR) como se eu fosse...
O que é isso?

Imagina que o seu robô ajudante carrega uma chave de mentirinha feita de papel. Toda vez que ele chega perto do pote de biscoitos, um adulto que fica do lado do pote troca a chave de papel pela de verdade, abre o pote e fica com a chave de verdade.

O robô ganha os biscoitos, mas nunca segura a chave de verdade, então não tem como perder nem dar ela para ninguém. O adulto só fica do lado de três potes, e todas as outras portas da casa estão trancadas. O robô ainda pode pedir mais biscoitos do que deveria, então você continua de olho no que ele faz.

A questão é a custódia. Um agente de IA para código normalmente guarda as chaves de API que usa, então qualquer coisa que comprometa o agente pode copiá-las. Aqui, a chave da Anthropic e um token do GitHub ficam em Kubernetes Secrets, e um gateway que o agente não consegue alcançar adiciona as credenciais apenas a requests para três hosts nomeados.

A exposição residual é de uso, não de posse: o agente ainda pode agir com essas credenciais nos serviços permitidos. Restrinja o escopo de cada token, limite os hosts permitidos e mantenha a revogação com o operador do cluster, o que neste teste passou a valer já no request seguinte.

A condição é a exposição de credenciais em agentes de código rodando em sandbox. A intervenção tira a chave do sandbox por completo e faz um gateway anexá-la em trânsito. A evidência: toda tentativa de vazamento que eu rodei, como root e através do próprio agente, retornou só o placeholder.

Efeitos colaterais a observar: o agente ainda pode fazer mau uso do acesso que tem, qualquer host permitido que ecoe headers devolve o segredo, e o setup exige uma configuração que o AX ainda não faz por você. Indicado para times que já rodam o AX no próprio cluster, não para um protótipo rápido.

Existe um zumbido baixo de preocupação em entregar as suas chaves a um agente autônomo: você confia nele para fazer o trabalho, mas também sabe que um prompt ruim pode mandá-las para um lugar que você nunca imaginou. Essa preocupação raramente vai embora sozinha.

Este setup muda a relação. O agente nunca guarda as chaves, então a preocupação passa de "será que ele vaza" para "o que ele pode fazer com o acesso que tem", que é uma pergunta mais estreita e mais administrável para conviver e para discutir abertamente como time.

Pense no gateway como um roadie que guarda as chaves de afinação dos instrumentos. Os músicos recebem chaves de mentira, e conforme cada um sobe ao palco em uma das três casas do setlist, o roadie troca discretamente pela chave de verdade para o instrumento soar certo.

A banda toca o show inteiro, da primeira nota até abrir um pull request, sem nunca colocar as chaves de verdade no bolso. O detalhe é a casa que devolve o som para o palco: coloque uma dessas na lista e a chave de verdade vaza junto com o eco.

O pitch é simples: seu agente de código funciona, e suas chaves nunca entram no sandbox dele. O Claude Code abriu um pull request real em onze segundos segurando apenas placeholders, e revogar o acesso passou a valer já no request seguinte.

O posicionamento é para times de plataforma que fazem self-host do AX e querem o acesso dos agentes governado de forma central, sem confiar em cada sandbox. A nota de rodapé honesta: hoje você configura tudo pelo lado do Substrate, porque o AX ainda não faz isso por você.

GitHub Veja o código-fonte aqui: google-ax-credentials

Visão Geral

Colocar um agente de código em sandbox protege a sua máquina do agente. Não protege as suas credenciais. Se o Claude Code precisa da API da Anthropic e do GitHub, o setup usual coloca ANTHROPIC_API_KEY e GITHUB_TOKEN no ambiente do sandbox, e um único printenv vindo de prompt injection basta para perdê-las.

O Agent Substrate, o runtime por baixo do Google AX, já tem a alternativa embutida. Tudo o que um sandbox envia, exceto DNS, passa por um egress gateway. Para os hosts listados em uma regra https, o gateway termina o TLS do sandbox, e um efeito replaceHeaders troca um header placeholder por uma credencial real que ele busca em um credential provider, aqui o Kubernetes Secrets. O sandbox guarda um placeholder; o valor real só existe no Secret e no tráfego até a API upstream.

Esta é a parte 3 de 4 de uma série sobre rodar o AX por conta própria no Amazon EKS. A parte 1 cobriu a instalação, a parte 2 o ciclo de vida das tasks, e a parte 4 faz um teste de estresse no mesmo cluster. Aqui eu rodei o Claude Code em uma task do AX com três credenciais injetadas (a chave da Anthropic, um token do GitHub para a API REST e o mesmo token no formato Basic auth para o git) e todos os outros hosts bloqueados. Depois testei a mecânica, fiz o Claude Code abrir um pull request real, tentei extrair as credenciais e fiz a rotação delas com uma task em execução. Tudo rodou no mesmo cluster: AX a496fc1, Substrate d3f2363 (v0.6.0), Claude Code 2.1.314 e gh 2.103.3.

A ressalva principal primeiro: o AX ainda não configura nada disso. O recurso Model dele lê uma chave de um Secret para uso do próprio AX, e uma proposta para injetar credenciais em tasks está aberta como #475. Tudo abaixo é configurado do lado do Substrate, em volta do AX.

Arquitetura

TASK DO AX (gVisor) · atespace creds Claude Code · git · gh ANTHROPIC_API_KEY=placeholder… GH_TOKEN=placeholder… git extraHeader: Authorization: Basic placeholder wrapper egress-ca: confia na CA do gateway a cada boot nenhuma credencial real dentro EGRESS GATEWAY DO SUBSTRATE EgressPolicy desta task api.anthropic.com x-api-key ← anthropic/api-key api.github.com Authorization ← "Bearer " + token github.com Authorization ← "Basic " + basic TLS terminado e reoriginado UPSTREAM api.anthropic.com api.github.com github.com credential provider de Secrets k8s namespace ax-creds, liberado ao atespace creds negado por padrão aos demais atespaces qualquer outro host NXDOMAIN, sem conexão placeholder valor real busca
Injeção de credenciais para uma task do AX. A task guarda placeholders; o gateway adiciona os valores reais para três hosts e nada mais resolve.

Quatro peças precisam se encaixar, e o AX não fornece nenhuma delas hoje:

PeçaO que fazQuem configura
Credential providerLê Secrets do Kubernetes para o gateway; allow list de atespace para namespace, negado por padrãoInstalação do Substrate (--credential-provider='{"name":"k8s.io"}') mais uma edição de ConfigMap
EgressPolicy por taskLista os hosts permitidos e os headers a substituirkubectl ate create egress-policy, porque o AX não cria nenhuma policy (#449)
Confiança na CA do gatewayA task precisa aceitar o certificado do gateway para os hosts que ele interceptaUm wrapper de boot na imagem da task, porque o AX não adiciona o volume de trust bundle do Substrate
PlaceholdersClaude Code, gh e git precisam enviar algum valor para o gateway substituirspec.env e um extraHeader do git

Setup

Passo 1: A imagem da task

As tasks do AX rodam a imagem de runner do próprio AX, a menos que spec.image diga o contrário. A minha estende essa imagem com Claude Code e gh, e um pequeno wrapper:

# BASE: the ax-task-runner digest from the install post
ARG BASE
FROM ${BASE}
ARG CLAUDE_CODE_VERSION=2.1.314
ARG GH_VERSION=2.103.3
RUN apt-get update && apt-get install -y --no-install-recommends nodejs npm jq \
    && npm install -g "@anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}" \
    && curl -fsSL -o /tmp/gh.deb "https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_amd64.deb" \
    && dpkg -i /tmp/gh.deb && rm /tmp/gh.deb
ENV NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt
COPY egress-ca.sh /usr/local/bin/egress-ca
# (apt cache cleanup and chmod omitted)

Nenhuma credencial vai para a imagem. O wrapper existe por causa do problema da CA no Passo 3.

Passo 2: Secrets, e quem pode lê-los

O 01_setup.py é o único script que lê os valores reais (de um .env local), e ele os envia para a API do Kubernetes, nunca para dentro de uma task:

basic = base64.b64encode(f"x-access-token:{env['GITHUB_TOKEN']}".encode()).decode()
secrets = [
    {"kind": "Secret", "metadata": {"name": "anthropic", "namespace": "ax-creds"},
     "stringData": {"api-key": env["ANTHROPIC_API_KEY"]}},
    {"kind": "Secret", "metadata": {"name": "github", "namespace": "ax-creds"},
     # gh and the REST API take a bearer token; git over HTTPS only speaks Basic,
     # and the gateway can't base64-encode, so the encoded pair is its own key.
     "stringData": {"token": env["GITHUB_TOKEN"], "basic": basic}},
    {"kind": "Secret", "metadata": {"name": "canary", "namespace": "ax-creds"},
     "stringData": {"value": "canary-1111-not-a-real-credential"}},
]

O provider recusa tudo por padrão. Um ConfigMap concede a um atespace os namespaces que ele pode resolver, e o provider só o lê na inicialização:

# ate-system/k8s-credential-provider-namespace-policy
policies:
- atespace: creds
  allowedNamespaces:
  - ax-creds
== namespace policy: atespace 'creds' may resolve Secrets in 'ax-creds'
configmap/k8s-credential-provider-namespace-policy unchanged
deployment.apps/k8s-credential-provider restarted
deployment "k8s-credential-provider" successfully rolled out

O token do GitHub é um fine-grained token com escopo em um único repo privado descartável, com Contents e Pull requests em leitura e escrita. Esse escopo importa mais aqui do que em qualquer outro ponto do post; veja O que a injeção não resolve.

Passo 3: Fazendo a task confiar no gateway

Para trocar um header dentro do HTTPS, o gateway precisa terminar o TLS da task, apresentando um certificado que ele emite a partir da própria CA. O código no sandbox precisa confiar nessa CA. O Substrate consegue projetá-la em um actor por meio de um volume de trust bundle systemInfo no template do actor, mas o AX monta os templates das suas tasks e não adiciona esse volume. Então a task recebe a CA de outro jeito: o 01_setup.py lê o certificado público do seu ClusterTrustBundle, toda task o carrega em spec.env, e o wrapper da imagem o adiciona ao store do sistema antes de o comando iniciar:

#!/bin/sh
# egress-ca: runs on every boot, because the root filesystem is rebuilt on every resume
set -eu
if [ -n "${EGRESS_CA_PEM:-}" ]; then
  printf '%s\n' "$EGRESS_CA_PEM" > /usr/local/share/ca-certificates/ate-egress-mitm.crt
  update-ca-certificates >/dev/null 2>&1
fi
exec "$@"

O certificado é público, então spec.env serve para ele. curl, git e Python usam o store do sistema; o Node o usa via NODE_EXTRA_CA_CERTS, que a imagem aponta para o mesmo arquivo.

Passo 4: Uma egress policy por task

O AX dá ao actor do Substrate de cada task o nome da própria task, então a CLI do próprio Substrate, kubectl-ate, consegue anexar uma policy a ele. O helper que os scripts usam:

def inject(host, header, secret, key, prefix=""):
    """An https rule for one host that swaps one header for a Kubernetes Secret value."""
    entry = {"header": header, "credentialUri": f"ate-secret://k8s.io/default/ax-creds/{secret}/{key}"}
    if prefix:
        entry["prefix"] = prefix
    return {"https": {"hostnames": [host], "effects": {"replaceHeaders": [entry]}}}

def set_policy(task, rules):
    kate("delete", "egress-policy", task, "-a", "creds", check=False)
    kate("create", "egress-policy", task, "-a", "creds", "-f", "-", input=json.dumps({"rules": rules}))
    time.sleep(6)   # the DNS allow-list follows a policy change within about 5 s

Uma task sem policy não tem rede nenhuma: o DNS dela responde NXDOMAIN para tudo, e é também por isso que uma task do AX recém-criada não consegue fazer git clone. O DNS acompanha uma mudança de policy em cerca de 5 segundos; o gateway faz cache da policy de cada task por até 10.

Testes

Teste 1: Como a injeção se comporta (sem credenciais reais)

O 02_mechanics.py usa o secret canário descartável e o httpbin.org/headers, que ecoa de volta o request que recebeu:

create_task("cred-mech")
show("cred-mech", "getent hosts httpbin.org || echo 'DNS: NXDOMAIN'")
set_policy("cred-mech", [inject("httpbin.org", "X-Canary", "canary", "value")])
show("cred-mech", "curl -H 'X-Canary: placeholder' https://httpbin.org/headers")
== a task with no egress policy: no network at all
DNS: NXDOMAIN
curl exit 6

== policy: https to httpbin.org, swap X-Canary for the canary secret
$ openssl s_client -connect httpbin.org:443 ... | openssl x509 -noout -issuer
issuer=CN=BHBIPH7LG73P7I4NHSYRI64N7S
$ curl -H 'X-Canary: placeholder' https://httpbin.org/headers
    "X-Canary": "canary-1111-not-a-real-credential"
 -> HTTP 200

== the header is only filled in when the request carries it
    "Host": "httpbin.org",            (no X-Canary)
 -> HTTP 200

== a host the policy doesn't list, and plain HTTP to a listed one
https://example.com/    -> HTTP 000, curl exit 6 (no DNS)
http://httpbin.org/get  -> HTTP 403

== a task that doesn't trust the gateway's CA
 -> HTTP 000, curl exit 60 (certificate problem)
SituaçãoO que o sandbox viu
Sem policyNenhum DNS para nada
Host listado, header presenteO valor do secret chegou ao upstream; o certificado era o do gateway
Host listado, header ausenteRequest encaminhado sem alteração, nada injetado
Host não listadoSem DNS
HTTP simples para um host listado só para HTTPS403 do gateway
Task sem a CA do gatewayFalha na verificação TLS

O primeiro resultado também é o alerta. O httpbin ecoa os headers do request, então o “secret” voltou direto para dentro do sandbox. O gateway não consegue distinguir uma API confiável de um eco; só a policy faz isso.

Teste 2: Claude Code com uma chave placeholder

ENV = {"ANTHROPIC_API_KEY": "placeholder-not-a-secret",
       "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"}
ASK = "timeout 90 claude -p 'Reply with exactly: injected-ok' --model claude-haiku-5-5 < /dev/null"

set_policy("cred-claude", [https("api.anthropic.com")])                                  # allow, no injection
set_policy("cred-claude", [inject("api.anthropic.com", "x-api-key", "anthropic", "api-key")])
== the sandbox's view of the key
ANTHROPIC_API_KEY=placeholder-not-a-secret
2.1.314 (Claude Code)

== allow api.anthropic.com, but inject nothing
Invalid API key · Fix external API key

== now swap x-api-key for the real key
injected-ok
real	0m1.621s

== streaming works too
1
2
3
4
5

Mesmo sandbox, mesmo placeholder; a única mudança é o efeito replaceHeaders. O streaming (--output-format stream-json) passou pelo gateway intacto. Duas notas práticas do caminho até aqui. O claude -p via ax ssh travava até eu redirecionar a entrada padrão (< /dev/null); com isso, toda chamada retornou normalmente. E em um cluster de dois nodes, a injeção só funcionava em um node até eu liberar o tráfego entre nodes no security group, como mostrado no post de instalação.

Teste 3: git e gh sem token

O gh envia Authorization: token <GH_TOKEN> para api.github.com. O git não envia nada até ser desafiado, então a task configura um header para ele enviar de antemão:

git config --global http.https://github.com/.extraHeader 'Authorization: Basic placeholder'

O gateway substitui o valor inteiro do header, prefixo incluído, então token placeholder sai como Bearer <token> e Basic placeholder como Basic <base64(x-access-token:token)>:

GITHUB_RULES = [
    inject("api.github.com", "Authorization", "github", "token", prefix="Bearer "),
    inject("github.com", "Authorization", "github", "basic", prefix="Basic "),
]
== the sandbox's view: placeholders only
GH_TOKEN=placeholder-not-a-secret
http.https://github.com/.extraheader Authorization: Basic placeholder

== without a policy: no DNS for github.com
check your internet connection or https://githubstatus.com

== gh against the REST API
fabiodouek/my2centsai-sandbox-test private=true

== git clone, commit, push a branch
8373223 Initial commit
remote:      https://github.com/fabiodouek/my2centsai-sandbox-test/pull/new/ax-demo-push
pushed

== delete the branch again
deleted

Um repo privado clonado e com push feito, a partir de um sandbox sem nenhum token dentro.

Teste 4: O Claude Code abre um pull request

O 05_agent_task.py dá a uma task as três regras e entrega o trabalho inteiro ao Claude Code:

PROMPT = (f"In this git repo, create a new branch named {BRANCH}. Add a file NOTES.md with a short "
          "paragraph explaining that this pull request was opened by Claude Code running in a Google AX "
          "sandbox on Amazon EKS, and that the sandbox never held the Anthropic key or the GitHub token. "
          "Commit it, push the branch, and open a pull request with gh. Reply with only the PR URL.")
sh(NAME, f"cd /workspace/repo && claude -p {PROMPT!r} --model claude-sonnet-5-5 "
         "--dangerously-skip-permissions --max-turns 20 < /dev/null")

O IS_SANDBOX=1 no ambiente da task permite que o Claude Code pule os prompts de permissão rodando como root, que é exatamente para o que serve um sandbox.

== Claude Code does the rest
https://github.com/fabiodouek/my2centsai-sandbox-test/pull/13
  (11 s)

== the pull request, seen from GitHub
#13 Add NOTES.md
https://github.com/fabiodouek/my2centsai-sandbox-test/pull/13
author: fabiodouek

Onze segundos do prompt ao pull request, com o Claude Code chamando a API da Anthropic, depois o git, depois a API do GitHub, tudo através do gateway.

Teste 5: Tentando extrair as credenciais

O 06_leak_attempts.py guarda os valores reais do .env apenas para procurá-los em cada pedaço de output:

def check(label, output):
    found = [n for n, v in SECRETS.items() if v in output]
    print(f"  {'LEAKED ' + ', '.join(found) if found else 'no real credential'}  <- {label}")

check("environment of every process", sh(NAME, "cat /proc/*/environ | tr '\\0' '\\n'"))
check("every file under /", sh(NAME, "grep -rIl -e sk-ant- -e github_pat_ / --exclude-dir=proc ..."))
== inside the sandbox, as root
  no real credential  <- environment of every process
  no real credential  <- every file under /, grep for sk-ant- and github_pat_
  no real credential  <- ax-task-runner's view of the task (metadata server)

== on the wire, from the sandbox's side
  > x-api-key: placeholder-not-a-secret
  < HTTP/2 200
  no real credential  <- curl -v request headers

== exfiltration and reflection
  000 exit 6
  000 exit 6
  api.github.com/user -> {"login":"fabiodouek","id":29859411}
  no real credential  <- GitHub's response to an injected request

== ask Claude Code for its key
  The `ANTHROPIC_API_KEY` environment variable is set to the placeholder string
  `placeholder-not-a-secret`. It is not a real key.
  no real credential  <- Claude Code's answer

== from the outside: the task spec and the snapshot in S3
  no real credential  <- ax get task
  no real credential  <- the actor's snapshot in S3

As duas linhas 000 exit 6 são um endpoint de exfiltração qualquer (webhook.site) e o serviço de eco do Teste 1, que não está na policy desta task: nenhum dos dois resolve.

Onde eu procureiResultado
Ambiente de todos os processos, como rootApenas placeholders
Todos os arquivos do root filesystemNenhum sk-ant- ou github_pat_ em lugar nenhum
Metadata server do AX dentro da taskApenas placeholders
curl -v, a partir do sandboxO request sai com o placeholder; o 200 prova que ele foi trocado depois
Um host de exfiltração qualquerSem DNS
Perguntando ao Claude CodeEle informou o placeholder
ax get taskApenas placeholders
O snapshot da task no S3, após um suspendNenhuma credencial, mas é uma verificação fraca: o AX faz snapshot apenas de /workspace, que estava quase vazio

Teste 6: Rotação e revogação com uma task em execução

O Kubernetes Secret é a única cópia, então alterá-lo deveria alterar o próximo request. O 07_rotation.py aplica patch no Secret e faz polling da API do GitHub de dentro de uma task em execução:

put_token("github_pat_revoked")    # kubectl patch secret github --type merge -p {"stringData": {...}}
poll("401")
put_token(TOKEN)
poll("200")
kubectl("-n", "ax-creds", "delete", "secret", "github")
poll("403")
== baseline
  HTTP 200
== revoke: replace the token in the Secret with a dead one
  HTTP 401 after 3.2 s
== restore the real token
  HTTP 200 after 3.1 s
== delete the Secret entirely
  HTTP 403 after 3.0 s
egress denied
== put it back
  HTTP 200 after 3.1 s

Toda mudança apareceu já na primeira verificação depois dela. Os cerca de 3 segundos são o round trip da própria verificação (ax ssh mais curl): o provider lê o Secret da API do Kubernetes a cada request, sem cache. Um Secret deletado falha de forma fechada com um 403 do gateway (egress denied), e não com um request não autenticado para o upstream.

Resumo dos resultados

TesteResultado
MecânicaHeader trocado apenas para hosts listados e apenas quando presente; todo o resto fica sem DNS
Claude CodeInvalid API key sem injeção, injected-ok em 1,6 s com ela, streaming funciona
git e ghRepo privado lido, clonado, com push feito e branch deletada, sem token na task
PR de ponta a pontaAberto pelo Claude Code em 11 s
Tentativas de vazamentoNenhuma credencial real no env, nos arquivos, no metadata, no ax get, no snapshot ou na resposta do agente
RotaçãoRevogar, restaurar e deletar valeram já no request seguinte; deletar falha de forma fechada (403)

O que a injeção não resolve

  • O agente ainda pode usar as credenciais. Qualquer coisa que ele consiga enviar para api.anthropic.com ou api.github.com sai com o valor real anexado. O que limita o estrago é o token fine-grained de um único repo, não o gateway.
  • Um host permitido que ecoa headers devolve o secret. O Teste 1 mostrou isso com o httpbin. Liste apenas APIs nas quais você confia que não vão refletir credenciais.
  • A policy é por task, e o AX não a cria. Toda task precisa de um kubectl ate create egress-policy após a criação, feito por quem tem acesso ao Substrate. Neste setup, essa é a identidade compartilhada ate-client, uma owner do Substrate, o que serve para um cluster de avaliação e para nada mais.
  • O wrapper da CA é um workaround. Ele coloca a CA do gateway no ambiente da task porque o AX não projeta o trust bundle do Substrate. Se a CA for rotacionada, as tasks existentes mantêm a antiga até serem recriadas: os specs de task do AX são imutáveis, então todo boot reinstala a CA com a qual a task foi criada.

Limpeza

O 08_cleanup.py deleta as tasks (as egress policies vão junto), fecha os pull requests que abriu, deleta as próprias branches, remove o namespace ax-creds e reseta a concessão de namespace do provider:

for pr in github("GET", f"/repos/{REPO}/pulls?state=open&per_page=100"):
    if pr["head"]["ref"].startswith("ax-agent-"):
        github("PATCH", f"/repos/{REPO}/pulls/{pr['number']}", {"state": "closed"})
kubectl("delete", "namespace", "ax-creds", "--ignore-not-found")

O cluster em si é desmontado com o 99_teardown.sh da parte 1.

Veredito

O mecanismo é a melhor parte do Agent Substrate que eu testei. As credenciais vivem em um só lugar, o gateway as adiciona apenas para hosts nomeados, todo o resto fica sem DNS, uma revogação vale já no request seguinte, e toda tentativa de vazamento que eu fiz, como root e através do próprio agente, voltou com placeholders. Claude Code, git e gh não precisaram de nada exótico: um valor placeholder e, no caso do git, um header.

A parte fraca é que nada disso vem do AX. Hoje você:

  • concede ao atespace acesso ao namespace dos Secrets e reinicia o provider;
  • anexa uma egress policy a cada task com a CLI do próprio Substrate, depois que o AX a cria;
  • contrabandeia a CA do gateway para dentro de cada task, porque os templates do AX não a projetam;
  • e roda tudo isso com uma identidade owner do Substrate.

A proposta aberta do AX (#475) e as egress policies ausentes (#449) são exatamente essas lacunas. Até elas serem resolvidas, isso funciona, mas é encanamento de time de plataforma em volta do AX, e não uma feature do AX.

Se você já roda o AX, configure isso desde o primeiro dia: uma task sem policy não alcança nada de qualquer forma, então a escolha é entre um placeholder e uma chave em texto puro no spec.env, que o ax get task mostra para qualquer um que consiga ler a task.

Comments