Writ

Infraestructura de permisos para agentes de IA: compuertas de aprobación para cada escritura que realice tu agente, con un registro de auditoría a prueba de manipulaciones.

Documentación

pywrit

Encuentra las escrituras peligrosas en el código de tu agente Python y luego protégelo. pywrit es el cliente Python y writ la CLI para Writ: una puerta de permiso/denegación que se sitúa delante de las escrituras consecuentes de tu agente (base de datos, HTTP, archivos, correo electrónico, colas, AWS) y registra un recibo encadenado por hash para cada decisión.

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 es local, determinista y gratuito: analiza tu código con el módulo ast de Python, no realiza llamadas de red y no necesita clave API.

Instalación

pip install pywrit

Requiere Python 3.9+. Instala el comando writ y el cliente Python pywrit.

Desarrolladores de JavaScript/TypeScript: ejecuta el escáner sin configuración de Python vía npm:

npx writ-scan .

El paquete writ-scan envuelve el mismo escáner (incluyendo soporte TS/JS). En la primera ejecución instala pywrit[polyglot] desde PyPI usando tu Python 3.9+ (una sola vez); después arranca al instante. Consulta npm/ para detalles, anulaciones de entorno (WRIT_PYTHON, WRIT_SCAN_NO_INSTALL) y la prueba de instalación.

Inicio rápido en 60 segundos: escanear → aplicar → proteger

1. Escanea. Ve qué funciones escriben y cuántas de ellas están protegidas.

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

También imprime un informe de riesgo (0-100, ponderado por nivel de riesgo), escribe los verbos descubiertos en writ-policy.json y muestra la instrumentación que añadiría como un diff unificado. Nada en tu código cambia todavía. writ scan . --score imprime solo el informe de riesgo.

2. Aplica. Inserta una puerta en la parte superior de cada función de escritura.

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

Cada función protegida ahora pregunta a Writ antes de escribir y falla de forma cerrada:

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

Vuelve a ejecutar writ scan . y verás gated: 4/4 (100%).

3. Protege. Obtén una clave API gratuita, carga la política descubierta y ejecuta tu 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 escritura protegida ahora recibe ALLOW, DENY o STEP_UP (un patrocinador humano debe aprobar), y cada decisión se escribe en el registro de auditoría a prueba de manipulaciones de tu 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

Qué detecta writ scan

Archivos Python (.py), analizados con el ast de la biblioteca estándar (sin dependencias adicionales):

CategoríaEjemplos
Base de datoscursor.execute(...) / executemany / executescript (solo SQL de escritura; SELECT/WITH/... omitidos), session.add / commit / delete / merge / flush
HTTPrequests.post / put / patch, client.delete(...), session.request(...)
Archivosopen(..., "w"/"a"/"x"/"+"), Path.write_text / write_bytes / unlink / rename, os.remove / rename / makedirs, shutil.rmtree / move / copy
Correo electrónicosendmail, send_message, send_email
Colaspublish, produce, enqueue, queue.send(...)
AWS SDKput_object, put_item, delete_item, upload_file, send_message, start_execution, ...
  • Cada escritura se asigna a un verbo como payments.refund o crm.update, inferido de la ruta del archivo y el nombre de la función.
  • Una función que ya llama a writ_check(...) / _writ_check(...) cuenta como protegida.
  • Omitidos: pruebas, directorios ocultos, virtualenvs, node_modules, dist, build. Usa --exclude SUBSTR (repetible) para omitir más.
  • No cubierto (revisar a mano): escrituras detrás de SQL construido dinámicamente, llamadas a SDK de terceros como stripe.Refund.create(...), colas de tareas diferidas o clientes compartidos varios niveles más abajo. El informe de riesgo lista estas brechas cada vez.

TypeScript / JavaScript (solo escaneo)

writ scan también encuentra sitios de escritura en TypeScript y JavaScript. Necesita el extra opcional (basado en tree-sitter):

pip install 'pywrit[polyglot]'
writ scan .

Sin el extra, el escáner imprime una pista de una línea y continúa — el escaneo de Python nunca lo necesita.

TS/JS es solo escaneo: los hallazgos se listan con verbos para tu archivo de política, pero writ scan --apply nunca reescribe archivos TS/JS — protégelos a mano. Patrones cubiertos: escrituras fetch/axios, escrituras fs, SQL a través de clientes estilo knex, escrituras de Prisma y llamadas a SDK de JS como stripe.refunds.create(...). (Todavía no cubierto: los equivalentes de SDK Python como stripe.Refund.create(...) — consulta "No cubierto" arriba.)

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

Acción de GitHub

Escanea cada solicitud de extracción en busca de sitios de escritura sin proteger. Los hallazgos aparecen como anotaciones de verificación en el archivo y línea exactos, más un informe de riesgo en Markdown en el resumen del trabajo. La verificación falla cuando un hallazgo sin proteger cumple tu umbral de fail-on-risk (predeterminado: high).

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

No se necesita clave API. Referencia completa: docs/github-action.md · flujo de trabajo de ejemplo: 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
    ...

¿Aún sin clave API? Prueba el sandbox sin clave:

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",
})

Lo que está cubierto:

  • check(...): la puerta. ALLOW / DENY / STEP_UP, más un recibo cada vez
  • verify_token(...): valida un token de autenticación ALLOW (detecta desviación de propósito)
  • grant(...): aprobación de patrocinador humano para la ruta STEP_UP
  • get_policy() / set_policy(...): gestiona la política del tenant
  • revoke(...) / reinstate(...) / revoked(): el interruptor de apagado
  • receipts() / receipt(id) / verify_chain() / stream_receipts(): el registro de auditoría
  • sandbox(...): prueba sin clave, no se requiere clave API

Referencia de 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)

Los comandos de token de patrocinador (revoke, reinstate, revoked, grant) leen --sponsor-token o WRIT_SPONSOR_TOKEN. Ejecuta writ --help para la lista completa de comandos.

Insignia de README

Muestra que las escrituras de tu agente están protegidas por Writ. Dos variantes:

Estática (funciona hoy, sin clave API) — mismo diseño, sin recuento en vivo:

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

Dinámica (recuento en vivo de escrituras protegidas de 30 días desde tu registro de auditoría) — acuña un token de insignia y luego incrusta el fragmento que devuelve la API:

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

El endpoint dinámico se envía con la puerta — hasta que esté en vivo, POST /v1/badge/tokens devuelve 404 y la insignia estática de arriba es la que debes usar.

Consulta badge/ para documentación de incrustación y el modelo de verificación (qué prueba la insignia — y qué no).

Documentación

Documentación completa: docs.withwrit.com · Inicio rápido: docs.withwrit.com/quickstart · Sitio: withwrit.com

Licencia

MIT