
Security News
upm Launches as a Fast, Tiny Package Manager Written in TypeScript
upm uses Node.js to deliver fast npm installs in about 250 KB, with a JavaScript API and security defaults.
@agentum/x402-spend-guard
Advanced tools
Trava financeira de saída pra agentes x402: teto por transação, teto diário agregado, allowlist de rede/ativo/destinatário/host, kill switch e circuit breaker por saúde real de outcome — o que o SDK oficial não cobre (SpendControls é só por transação).
Trava financeira de saída pra quem constrói um agente que paga via x402 — o lado que gasta, não o que vende.
O SDK oficial (@x402/core) só trava por transação (SpendControls.maxAmountPerPayment). Não existe:
payTo) ou host do recurso,Nem A2A nem MCP preenchem esse gap — nenhum dos dois tem conceito de orçamento/política de gasto do lado do requisitante.
Uma camada fora do caminho de decisão do código que assina a transação — ele nunca vê nem controla os limites, só recebe { allowed, reason }.
node:sqlite, zero dependência nova)chmod 444 de fricção extraaccepts[] vazio/malformado — tudo vira bloqueio, nunca exceção não tratadaGET /health)Testado em produção real: o teto/allowlist/kill switch é a mesma trava usada pelo Payment Agent da AGENTUM desde 2026-09-05, com pagamentos reais em Base mainnet. O circuit breaker (v1.1.0) é novo e ainda não passou por produção — testado com concorrência real entre processos (não só chamadas no mesmo processo) antes do release, mas sem histórico de uso real ainda.
npm install @agentum/x402-spend-guard
const { SpendGuard } = require("@agentum/x402-spend-guard");
const guard = new SpendGuard({
maxPerTransactionUnits: 100_000, // 0,10 USDC (6 casas decimais)
dailyCapUnits: 1_000_000, // 1,00 USDC/dia
allowedNetworks: ["eip155:8453"], // Base mainnet
allowedAssets: ["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"], // USDC na Base
allowedPayTo: ["0xSeuDestinatarioConfiavel..."],
allowedResourceHosts: ["api.confiavel.com"],
});
// depois de receber um 402 de um servidor x402, antes de assinar qualquer coisa:
const decision = guard.evaluateAccepts(paymentRequired.accepts, resourceUrl);
if (!decision.allowed) {
throw new Error(`Pagamento bloqueado pela política: ${decision.reason}`);
}
// ... assine e envie o pagamento normalmente (wrapFetchWithPayment, etc) ...
// depois de saber o resultado real do envio:
guard.logOutcome({ outcome: "settled", amountUnits: decision.amountUnits, resourceUrl });
Todas as opções de configuração são obrigatórias e validadas no construtor — uma allowlist vazia ou ausente por engano lança erro na hora, em vez de silenciosamente virar "aceita qualquer coisa".
npx x402-kill-switch status
npx x402-kill-switch on "investigando comportamento suspeito"
npx x402-kill-switch off
# com caminho customizado (senão usa ./data/kill-switch.json):
npx x402-kill-switch on "motivo" --path /caminho/seguro/kill-switch.json
O kill switch só é lido pelo SpendGuard — nenhuma função de escrita existe na lib em si. A única forma de ligar/desligar é rodando o CLI manualmente. Isso garante que o próprio código que gasta dinheiro nunca tem, à disposição, uma função capaz de se autodesbloquear.
Importante: isso protege contra escrita, não contra deleção. Se o arquivo do kill switch for apagado (não editado, apagado), isKillSwitchActive() trata isso como "nunca foi ativado" — ou seja, apagar um kill switch ATIVO o desliga silenciosamente. Garanta que o processo que gasta dinheiro não tenha permissão de escrita no diretório onde esse arquivo vive, não só no arquivo em si.
Todo "circuit breaker"/"failover" que existe hoje pro x402 monitora se um endpoint responde (polling de liveness). Nenhum monitora se ele entrega quando alguém tenta pagar de verdade. Esta lib deriva a saúde do host a partir do que você já reporta em logOutcome() — sem outcome real, sem dado, sem custo extra.
const guard = new SpendGuard({
// ...allowlists de sempre...
circuitBreaker: { failureThreshold: 3, cooldownMs: 60_000 }, // opcional -- ausente = comportamento idêntico à v1.0.x
});
const decision = guard.evaluateAccepts(paymentRequired.accepts, resourceUrl);
if (!decision.allowed) {
// decision.reason pode ser "circuit_open" agora -- 3 outcomes reais
// seguidos que não foram "settled" pra esse ENDPOINT (host+path), e o cooldown ainda não expirou.
}
// sempre chamar logOutcome depois do resultado real -- é isso que alimenta o circuit breaker
guard.logOutcome({ outcome: "settled" /* ou "settle_failed", "network_error", etc */, amountUnits, resourceUrl });
closed (normal) → open (depois de N falhas consecutivas, bloqueia) → half_open (cooldown expirou, deixa passar 1 sonda de teste) → closed de novo se a sonda vier settled, ou open de novo (reinicia o cooldown) se falhar./rota-a e /rota-b do mesmo domínio têm saúde INDEPENDENTE; a mesma rota com query string diferente (?id=1 vs ?id=2) continua compartilhando saúde, é o mesmo endpoint. Corrigido depois de tentar usar o failover de verdade entre uma rota real e seu espelho no mesmo domínio (v1.1.0 agrupava por host inteiro, o que fazia o "espelho" nunca poder ser escolhido — tinha sempre a mesma saúde da rota principal).circuitBreaker na config, nada disso roda — só o teto/allowlist de sempre. Consultar saúde manualmente com guard.getEndpointHealth(url) funciona mesmo sem habilitar o bloqueio automático.guard.pickHealthyResource([urlPrincipal, urlEspelho, ...]) devolve a primeira URL cujo endpoint (host+path) não está open, ou null se todas estiverem — funciona mesmo que os espelhos estejam no MESMO domínio da rota principal. A lib nunca descobre espelhos sozinha, só ajuda a escolher entre os que você já conhece.store customizado escrito antes desta versão (sem .health) continua funcionando exatamente como antes — o circuit breaker simplesmente nunca bloqueia nesse caso (best-effort, nunca lança).Por padrão, SpendGuard cria seu próprio SpendStore (SQLite em ./data/x402-spend-guard.sqlite). Se precisar compartilhar o mesmo ledger entre múltiplos hosts, implemente a mesma interface (reserve/getSpentToday/logDecision/isKillSwitchActive) sobre Redis/Postgres e passe via new SpendGuard({ ..., store: meuStoreCustomizado }).
evaluateAccepts() antes de assinar — nada intercepta automaticamente um caminho de saída novo que não a chame.chmod 444, não isolamento de SO real — protege contra escrita, não contra deleção do arquivo (ver seção "Kill switch" acima).SpendStore padrão cobre múltiplos processos num host só, não múltiplos hosts. Sob concorrência real (múltiplos processos), o SQLite espera o lock soltar (PRAGMA busy_timeout) em vez de falhar na hora — mas ainda serializa, não paraleliza: throughput alto concorrente pode ficar lento, não incorreto.SpendGuard no mesmo processo sem passar store explícito pra cada um, os dois vão compartilhar o mesmo arquivo SQLite padrão (./data/x402-spend-guard.sqlite) e portanto o mesmo teto diário acumulado — provavelmente não é o que você quer. Passe um store com dbPath próprio pra cada guard se precisar de políticas independentes.settled conta igual). Um provedor genuinamente saudável pode ficar bloqueado por alguns minutos por uma sequência de erro transitório do SEU lado (rede, nonce, etc), não necessariamente do lado dele.SpendStore (mesmo SQLite do teto diário) — múltiplos processos no mesmo host compartilham (se apontarem pro mesmo dbPath), múltiplos hosts, não./users/123 vs /users/456, se você usar esse estilo de URL) vira uma chave DIFERENTE por valor, e as falhas nunca se acumulam o suficiente pra abrir o circuito dessa rota. Funciona bem pra rotas com parâmetros só em query string (/users?id=123 — ignorado na chave) ou pra espelhos com paths fixos (/rota vs /rota-mirror), que foi o caso real que motivou a feature.open sob a chave antiga (só host) não é migrado, reabre como closed até a próxima falha real. Sem risco de gasto indevido (é só a trava de saúde relaxando, o teto/allowlist continuam intactos), mas vale saber se você rodou a v1.1.0 por mais que algumas horas antes de atualizar.PaymentRequirements.extra.splits, proposto na PR #3221 do x402 core — ainda não é spec oficial hoje). checkOption() só confere o payTo/amount do nível principal de cada opção em accepts[]; se um esquema de pagamento dividido virar padrão, uma perna secundária (taxa de plataforma, referral) poderia sair da allowlist de destinatário ou empurrar o gasto agregado além do teto sem a guard perceber. Achado real, levantado por @whawk46 — rastreado aqui, não implementado ainda porque o campo não existe em nenhuma resposta real de servidor x402 hoje.MIT
FAQs
Trava financeira de saída pra agentes x402: teto por transação, teto diário agregado, allowlist de rede/ativo/destinatário/host, kill switch e circuit breaker por saúde real de outcome — o que o SDK oficial não cobre (SpendControls é só por transação).
The npm package @agentum/x402-spend-guard receives a total of 65 weekly downloads. As such, @agentum/x402-spend-guard popularity was classified as not popular.
We found that @agentum/x402-spend-guard demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
upm uses Node.js to deliver fast npm installs in about 250 KB, with a JavaScript API and security defaults.

Company News
Socket is joining the OpenJS Security Stewardship Program to fund Node.js vulnerability research, maintainer remediation, and security releases.

Security News
Two compromised GitHub Actions were re-enabled with malicious tags intact, exposing thousands of downstream repositories to Mini Shai-Hulud.