Writ

Infraestrutura de permissões para agentes de IA — portões de aprovação para cada escrita que seu agente faz, com um registro de auditoria à prova de adulteração.

Documentação

pywrit

Encontre as escritas perigosas no código do seu agente Python e bloqueie-as. pywrit é o cliente Python e writ é a CLI para Writ: um bloqueio de permitir/negar que fica na frente das escritas consequentes do seu agente (banco de dados, HTTP, arquivos, e-mail, filas, AWS) e registra um recibo com hash encadeado para cada decisão.

writ scan finds 4 write sites in a Python agent (0/4 gated); writ scan --apply inserts gates; a re-scan shows 4/4 gated

writ scan é local, determinístico e gratuito: ele analisa seu código com o módulo ast do Python, não faz chamadas de rede e não precisa de chave de API.

Instalação

pip install pywrit

Requer Python 3.9+. Instala o comando writ e o cliente Python pywrit.

Desenvolvedores JavaScript/TypeScript: execute o scanner sem configuração Python via npm:

npx writ-scan .

O pacote writ-scan encapsula o mesmo scanner (incluindo suporte a TS/JS). Na primeira execução, ele instala pywrit[polyglot] do PyPI usando seu Python 3.9+ (uma única vez); depois disso, inicia instantaneamente. Veja npm/ para detalhes, substituições de ambiente (WRIT_PYTHON, WRIT_SCAN_NO_INSTALL) e o teste de instalação.

Início rápido em 60 segundos: escanear → aplicar → bloquear

1. Escaneie. Veja quais funções escrevem e quantas delas estão bloqueadas.

writ scan .
writ scan: /path/to/support-agent
  files scanned: 4  skipped: 0
  write sites: 4 in 3 function(s)
  gated: 0/4 (0%)
  verbs discovered: 3
    crm.update
    payments.refund
    tickets.close

Ele também imprime um relatório de risco (0-100, ponderado por nível de risco), grava os verbos descobertos em writ-policy.json e mostra a instrumentação que adicionaria como um diff unificado. Nada no seu código muda ainda. writ scan . --score imprime apenas o relatório de risco.

2. Aplique. Insira um bloqueio no topo de cada função de escrita.

writ scan . --apply        # shows the diff, then asks before writing
writ scan . --apply --yes  # no prompt (e.g. in CI)

Cada função bloqueada agora pergunta ao Writ antes de escrever e falha de forma segura:

def issue_refund(charge_id, amount_cents):
    if _writ_check("payments.refund") != "ALLOW":
        raise PermissionError("writ denied payments.refund")
    ...

Execute writ scan . novamente e você verá gated: 4/4 (100%).

3. Bloqueie. Obtenha uma chave de API gratuita, carregue a política descoberta e execute seu agente.

writ key --email you@example.com                  # free API key + tenant
export WRIT_API_KEY=writ_...                      # read by the inserted gate
export WRIT_SPONSOR=acme WRIT_AGENT=support-agent # optional: who is acting
writ scan . --push-policy --key "$WRIT_API_KEY"   # upload the discovered verb policy

Cada escrita bloqueada agora recebe ALLOW, DENY ou STEP_UP (um patrocinador humano deve aprovar), e cada decisão é gravada no log de auditoria à prova de adulteração do seu tenant:

writ receipts --key "$WRIT_API_KEY"       # latest receipts
writ verify-chain --key "$WRIT_API_KEY"   # verify the receipt hash chain
writ stream --key "$WRIT_API_KEY"         # tail decisions live
writ report --key "$WRIT_API_KEY"         # Agent Action Report

O que o writ scan detecta

Arquivos Python (.py), analisados com o ast da biblioteca padrão (sem dependências extras):

CategoriaExemplos
Banco de dadoscursor.execute(...) / executemany / executescript (somente SQL de escrita; SELECT/WITH/... ignorados), session.add / commit / delete / merge / flush
HTTPrequests.post / put / patch, client.delete(...), session.request(...)
Arquivosopen(..., "w"/"a"/"x"/"+"), Path.write_text / write_bytes / unlink / rename, os.remove / rename / makedirs, shutil.rmtree / move / copy
E-mailsendmail, send_message, send_email
Filaspublish, produce, enqueue, queue.send(...)
AWS SDKput_object, put_item, delete_item, upload_file, send_message, start_execution, ...
  • Cada escrita é mapeada para um verbo como payments.refund ou crm.update, inferido do caminho do arquivo e do nome da função.
  • Uma função que já chama writ_check(...) / _writ_check(...) conta como bloqueada.
  • Ignorados: testes, diretórios ocultos, virtualenvs, node_modules, dist, build. Use --exclude SUBSTR (repetível) para ignorar mais.
  • Não coberto (revise manualmente): escritas por trás de SQL construído dinamicamente, chamadas de SDK de terceiros como stripe.Refund.create(...), filas de tarefas adiadas ou clientes compartilhados várias camadas abaixo. O relatório de risco lista essas lacunas toda vez.

TypeScript / JavaScript (somente escaneamento)

writ scan também encontra locais de escrita em TypeScript e JavaScript. Ele precisa do extra opcional (baseado em tree-sitter):

pip install 'pywrit[polyglot]'
writ scan .

Sem o extra, o scanner imprime uma dica de uma linha e continua — o escaneamento Python nunca precisa dele.

TS/JS é somente escaneamento: os achados são listados com verbos para seu arquivo de política, mas o writ scan --apply nunca reescreve arquivos TS/JS — bloqueie-os manualmente. Padrões cobertos: escritas fetch/axios, escritas fs, SQL por meio de clientes estilo knex, escritas Prisma e chamadas de SDK JS como stripe.refunds.create(...). (Ainda não coberto: os equivalentes de SDK Python como stripe.Refund.create(...) — veja "Não coberto" acima.)

writ scan on a TypeScript agent finds 5 write sites, 1 of 5 gated

GitHub Action

Escaneie cada pull request em busca de locais de escrita não bloqueados. Os achados aparecem como anotações de verificação no arquivo e linha exatos, além de um relatório de risco em Markdown no resumo do job. A verificação falha quando um achado não bloqueado atende ao seu limite de fail-on-risk (padrão: high).

- uses: actions/checkout@v4
- uses: AvenueDAdmin/pywrit@v0
  with:
    fail-on-risk: high   # high | medium | low | never

Nenhuma chave de API necessária. Referência completa: docs/github-action.md · exemplo de workflow: examples/github-action/writ-scan.yml

Cliente Python

from pywrit import WritClient

client = WritClient(api_key="writ_...")

result = client.check(
    sponsor_id="acme",
    agent_id="agent-7",
    verb="db.write",
    target="prod.customers",
    purpose="backfill region field",
)

if result.decision == "ALLOW":
    # result.auth_token is a short-lived token bound to this exact write
    perform_write(...)
elif result.decision == "STEP_UP":
    # a human sponsor must approve first: client.grant(...), then re-check
    ...
else:
    # DENY
    ...

Ainda sem chave de API? Experimente o sandbox sem chave:

client = WritClient()
client.sandbox({
    "sponsorId": "acme",
    "agentId": "agent-7",
    "verb": "demo_write",       # sandbox only allows demo_write ...
    "target": "demo-customers", # ... on targets starting with demo-
    "purpose": "trying the gate",
})

O que está coberto:

  • check(...): o bloqueio. ALLOW / DENY / STEP_UP, além de um recibo toda vez
  • verify_token(...): valida um token de autenticação ALLOW (detecta desvio de propósito)
  • grant(...): aprovação de patrocinador humano para o caminho STEP_UP
  • get_policy() / set_policy(...): gerencia a política do tenant
  • revoke(...) / reinstate(...) / revoked(): o interruptor de emergência
  • receipts() / receipt(id) / verify_chain() / stream_receipts(): o log de auditoria
  • sandbox(...): teste sem chave, nenhuma chave de API necessária

Referência da CLI

writ check --key writ_... --sponsor acme --agent agent-7 \
  --verb db.write --target prod.customers --purpose "backfill region field"
writ policy --key writ_... --set payments.refund require_grant
writ revoke --sponsor acme --agent agent-7 --reason "runaway loop"   # kill switch (sponsor token)
writ grant --sponsor acme --agent agent-7 --verb payments.refund \
  --target ch_123 --purpose "approved refund"                        # STEP_UP approval (sponsor token)

Comandos de token de patrocinador (revoke, reinstate, revoked, grant) leem --sponsor-token ou WRIT_SPONSOR_TOKEN. Execute writ --help para a lista completa de comandos.

Selo do README

Mostre que as escritas do seu agente são bloqueadas pelo Writ. Dois sabores:

Estático (funciona hoje, sem chave de API) — mesmo design, sem contagem ao vivo:

[![agent writes gated by Writ](https://cdn.jsdelivr.net/gh/AvenueDAdmin/pywrit@main/badge/writ-gated.svg)](https://withwrit.com)

Dinâmico (contagem ao vivo de escritas bloqueadas em 30 dias do seu log de auditoria) — crie um token de selo e incorpore o trecho que a API retorna:

curl -s https://api.withwrit.com/v1/badge/tokens \
  -H "Authorization: Bearer writ_..." \
  -H "Content-Type: application/json" \
  -d '{}'

O endpoint dinâmico acompanha o bloqueio — até estar ativo, POST /v1/badge/tokens retorna 404 e o selo estático acima é o que deve ser usado.

Veja badge/ para documentação de incorporação e o modelo de verificação (o que o selo prova — e o que não prova).

Documentação

Documentação completa: docs.withwrit.com · Início rápido: docs.withwrit.com/quickstart · Site: withwrit.com

Licença

MIT