Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
o1js-scan — Analizador estático sin dependencias para errores de solidez en circuitos zk en o1js/Mina zkApps y circuitos Noir | Kitploit
Herramientas/GitHubGitHub/auditinfra-io/o1js-scan
Herramientas DefensivasAnálisis EstáticoEscáneres de VulnerabilidadesAnálisis Estático de Código (SAST)Análisis de VulnerabilidadesAnálisis de CódigoCriptografíaDevSecOps
GitHubauditinfra-io/o1js-scan

o1js-scan

Analizador estático sin dependencias para errores de solidez en circuitos zk en o1js/Mina zkApps y circuitos Noir

Ver Repositorio
210hace 4 díasAún no revisado

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Sitio web
Compartir

o1js-scan

CI Python License PyPI npm

Paquete de la comunidad: o1js-scan está listado en el directorio oficial de Paquetes de la Comunidad de o1js.

Última versión: 0.20.0 — el analizador ahora lee contratos que extends TokenContract. Hasta esta versión, la puerta de contratos coincidía únicamente con SmartContract, por lo que cada token fungible, colección NFT y pool AMM del ecosistema se escaneaba como "sin hallazgos". Si escaneaste un contrato de token antes de la 0.20.0, vuelve a escanearlo. Consulta el .

CHANGELOG

Un analizador estático rápido y sin dependencias para bugs de solidez en circuitos zk en:

  • o1js / Mina zkApps (TypeScript .ts / .js) — circuitos Kimchi a partir de cuerpos @method
  • Noir (.nr) — el DSL ZK de Aztec con sintaxis similar a Rust (incluyendo patrones con forma de aztec-nr)

Los bugs críticos para la seguridad normalmente no están en el sistema de pruebas — están en las restricciones propias de la aplicación: testigos que el probador controla pero que el circuito nunca vincula. o1js-scan es el escáner de señales sub-restringidas para los primos de Circom en los ecosistemas de Mina y Noir.```bash pip install o1js-scan

or: pipx install o1js-scan

or: npm install -D o1js-scan

o1js-scan path/to/zkapp # o1js + Noir (auto) noir-scan path/to/circuits # same binary — Noir-friendly alias noir-scan . --lang noir --fail-on high --sarif noir.sarif

root@kitploit:~
### Ejemplo

Dado un vault cuyo monto de `withdraw` es un testigo controlado por el probador que
nunca está vinculado al estado on-chain:```console
$ o1js-scan examples/vulnerable_vault.ts --include-examples
LOW      O1JS_UNCONSTRAINED_RECIPIENT       vulnerable_vault.ts:23  fn=withdraw  Recipient `to` is prover-chosen in `withdraw`
HIGH     O1JS_UNCONSTRAINED_WITNESS         vulnerable_vault.ts:23  fn=withdraw  Unconstrained witness `amount` flows to send_amount in `withdraw`
o1js-scan: 2 finding(s) [1 high, 1 low] in 1 of 1 file(s) — fails (--fail-on high)
$ echo $?
1

--include-examples es necesario aquí solo porque el archivo de demostración se encuentra bajo examples/, que el clasificador de rutas degrada por defecto para que el código de ejemplo de un repositorio no pueda hacer fallar su compilación. El mismo contrato en tu src/ reporta HIGH sin ninguna bandera.

El hallazgo HIGH es el bug que permite drenar fondos. El contrato corregido (examples/safe_vault.ts) lo elimina y sale con 0, conservando solo el LOW informativo sobre el destinatario elegido por el probador:```console $ o1js-scan examples/safe_vault.ts --include-examples LOW O1JS_UNCONSTRAINED_RECIPIENT safe_vault.ts:23 fn=withdraw Recipient to is prover-chosen in withdraw o1js-scan: 1 finding(s) [1 low] in 1 of 1 file(s) — passes (--fail-on high) $ echo $? 0

root@kitploit:~
Consulta [`examples/`](https://github.com/auditinfra-io/o1js-scan/blob/main/examples) para los pares vulnerable/corregido de o1js y Noir.

## Contenido

- [Instalación](#install)
- [Uso](#usage) · [Suprimir un hallazgo](#suppressing-a-reviewed-finding)
- [GitHub Action](#github-action)
- [Qué detecta — o1js](#what-it-detects-o1js) · [Noir](#what-it-detects-noir)
- [Limitaciones conocidas](#known-limitations) · [Dónde se detiene esta herramienta](#where-this-tool-stops)
- [Privacidad y código privado](#privacy-and-private-code)
- [Revisión post-cuántica](#post-quantum-review)
- [Compatibilidad](#compatibility) · [Cómo funciona](#how-it-works)
- [Contribuir](#roadmap--contributing)

## Instalación```bash
pip install o1js-scan

Para una instalación global aislada de la CLI, use pipx:```bash pipx install o1js-scan

root@kitploit:~
Para repositorios de aplicaciones basados en Node/npm como Noir, Aztec u o1js, instala el wrapper de npm:```bash
npm install -D o1js-scan
npx noir-scan . --lang noir --fail-on high

El paquete npm es un envoltorio ligero alrededor del mismo analizador de Python y requiere Python 3.8+ en el PATH (python3 o python). Establece O1JS_SCAN_PYTHON para elegir un intérprete específico.

O desde el código fuente:```bash git clone https://github.com/auditinfra-io/o1js-scan cd o1js-scan pip install -e .

root@kitploit:~
Sin dependencias de Python de terceros. Python 3.8+. El script de consola `noir-scan` se
instala junto con `o1js-scan` (mismo punto de entrada), incluso a través del wrapper
de npm.

## Uso```bash
# scan a directory (recursively; skips node_modules, target/, .git, …)
o1js-scan path/to/project

# Noir-only / o1js-only
noir-scan circuits --lang noir
o1js-scan src --lang o1js

# scan a single file
o1js-scan src/MyContract.ts
noir-scan src/main.nr

# machine-readable output for CI
o1js-scan src --json

# SARIF 2.1.0 for GitHub code scanning (writes o1js-scan.sarif by default)
o1js-scan src --sarif
noir-scan . --lang noir --sarif noir.sarif

# choose which severity fails CI (critical|high|medium|low|none; default high)
o1js-scan src --fail-on medium

# progressive/power-user gate (equivalent to --fail-on medium)
o1js-scan src --strict

# test code is excluded by default (both backends); opt back in
o1js-scan src --include-tests

# example code is downgraded to LOW by default; keep original severity
o1js-scan src --include-examples

o1js-scan --version

El código de salida es 1 cuando hay un hallazgo igual o superior al nivel de --fail-on (por defecto high) y 0 en caso contrario, por lo que puedes integrarlo directamente en CI. Con el valor predeterminado, un hallazgo de severidad baja/media (incluida la regla informativa de destinatarios que se describe a continuación) no hace fallar la compilación; usa --fail-on none para solo informar, o --strict (una forma abreviada de --fail-on medium) para aplicar un filtro más estricto mientras sigues tratando los hallazgos de severidad baja como informativos. Las dos opciones son mutuamente excluyentes, por lo que la configuración de CI no puede ser ambigua. Una ruta de escaneo inexistente sale con 2 y un error en stderr, de modo que un error tipográfico no puede pasar silenciosamente por CI como una ejecución limpia. Cada ejecución imprime un resumen de una línea (recuentos por severidad y el veredicto del filtro) en stderr.

El código de prueba se excluye por defecto — en ambos backends. Las pruebas construyen deliberadamente valores inválidos y transacciones incorrectas para demostrar que las aserciones las rechazan, por lo que un hallazgo allí es el objetivo de la prueba y no un error del circuito. Un archivo cuenta como código de prueba cuando:

  • su nombre coincide con *.test.ts / *.spec.ts (y las variantes .js/.jsx/.tsx/.mjs/.cjs), o *_test.nr / test_*.nr;
  • se encuentra bajo un directorio test/, tests/, __tests__/, spec/ o __mocks__/;
  • (solo Noir, basado en contenido) la función lleva un atributo #[test] / #[test(...)], o se encuentra dentro de un bloque mod test { … } / mod tests { … } — con alcance de bloque, por lo que un módulo de prueba al final de un archivo de producción no silencia el resto de este.

Pasa --include-tests para informarlos.

El código de ejemplo se degrada, no se descarta. Un hallazgo en un directorio examples/ o example/, o en un archivo llamado *.eg.ts (también .nr y las demás extensiones de JS/TS), se reduce a LOW con una nota — sigue informándose, pero ya no puede hacer fallar una compilación. El código de ejemplo se simplifica deliberadamente, y señalar los propios ejemplos de un framework como vulnerabilidades es ruido; pero se copia a producción con mucha más frecuencia que el código de prueba, por lo que se degrada en lugar de ocultarse. Pasa --include-examples para mantener la severidad original.

Siempre que se aplique cualquiera de las dos políticas, la ejecución imprime una línea en stderr indicándolo — p. ej. 6 file(s) skipped as test code, 1 finding(s) downgraded as examples — para que un escaneo silencioso nunca sea silenciosamente silencioso. Los recuentos también aparecen en SARIF bajo invocation.properties. Ten en cuenta la contrapartida: la detección es solo basada en rutas (sin análisis de describe(/it(), por lo que un circuito de producción almacenado bajo tests/ sí se omitirá — la línea de stderr es cómo te das cuenta.

Directorios omitidos al recorrer un árbol: node_modules, target (nargo), .git, dist, build, __pycache__, .venv, venv.

Suprimir un hallazgo revisado

Silencia un hallazgo que hayas triado sin relajar el filtro, con un comentario en línea sobre — o en la línea anterior a — la línea señalada:```ts this.send({ to, amount }); // o1js-scan-disable-line O1JS_UNCONSTRAINED_WITNESS

// o1js-scan-disable-next-line this.send({ to, amount });

root@kitploit:~
| `-l` | `--list` | List all available modules |
| `-m` | `--module` | Specify the module to use |
| `-o` | `--output` | Specify the output file |
| `-p` | `--proxy` | Specify the proxy to use |
| `-r` | `--report` | Generate a report |
| `-s` | `--silent` | Silent mode |
| `-t` | `--threads` | Specify the number of threads |
| `-u` | `--url` | Specify the target URL |
| `-v` | `--verbose` | Verbose mode |
| `-w` | `--wordlist` | Specify the wordlist to use |
| `-x` | `--exclude` | Exclude specific modules |

### Examples

```bash
# Scan a single target
python3 darkus.py -u https://example.com

# Scan multiple targets from a file
python3 darkus.py -f targets.txt

# Use a specific module
python3 darkus.py -u https://example.com -m sqli

# Generate a report
python3 darkus.py -u https://example.com -r report.html

Modules

ModuleDescription
sqliSQL Injection scanner
xssCross-Site Scripting scanner
lfiLocal File Inclusion scanner
rfiRemote File Inclusion scanner
ssrfServer-Side Request Forgery scanner
csrfCross-Site Request Forgery scanner
open-redirectOpen Redirect scanner
crlfCRLF Injection scanner
xxeXML External Entity scanner
sstiServer-Side Template Injection scanner

Configuration

The configuration file is located at config/config.yaml. You can modify the following settings:

root@kitploit:~
# General settings
general:
  threads: 10
  timeout: 30
  user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"

# Proxy settings
proxy:
  enabled: false
  http: "http://127.0.0.1:8080"
  https: "https://127.0.0.1:8080"

# Output settings
output:
  format: "html"
  directory: "reports"

Disclaimer

This tool is intended for educational purposes and authorized security testing only. The author is not responsible for any misuse or damage caused by this tool. Always obtain proper authorization before testing any system.```nr let inv = unsafe { hint(x) }; // o1js-scan-disable-line NOIR_UNCONSTRAINED_WITNESS

root@kitploit:~
Enumere uno o más ids de reglas para suprimir solo esas; una directiva sin ids
suprime todas las reglas en la línea objetivo.

Como biblioteca:```python
from o1js_scan import analyze_file, analyze_project

for path, finding in analyze_project("src", lang="auto"):
    print(path, finding.rule_id, finding.severity.value, finding.title)

GitHub Action

Añade el escáner a CI en unas pocas líneas. Los hallazgos aparecen como anotaciones en el diff del PR y como alertas en la pestaña Security → Code scanning del repositorio.```yaml

.github/workflows/o1js-scan.yml

name: o1js-scan on: [push, pull_request]

permissions: contents: read security-events: write # required to upload SARIF to code scanning

jobs: scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: auditinfra-io/[email protected] with: path: src # optional, defaults to the repo root lang: auto # auto | o1js | noir # version: 0.20.0 # optional, pin the scanner version # fail-on: high # optional, fail the job on high/critical

root@kitploit:~
### Receta de CI solo para Noir

Recomendada para proyectos Noir que quieren alertas de escaneo de código y una puerta de alta severidad:```yaml
- uses: auditinfra-io/[email protected]
  with:
    path: .
    lang: noir
    fail-on: high

O sin la Acción:```bash pip install o1js-scan noir-scan . --lang noir --fail-on high --sarif noir.sarif

root@kitploit:~
### pre-commit (opcional)```yaml
# .pre-commit-config.yaml
- repo: local
  hooks:
    - id: noir-scan
      name: noir-scan
      entry: noir-scan
      language: system
      pass_filenames: false
      args: [".", "--lang", "noir", "--fail-on", "high"]

Entradas: path (por defecto .), lang (auto|o1js|noir, por defecto auto), version (versión de PyPI a instalar, por defecto la última), upload-sarif (por defecto true), fail-on (critical|high|medium|low|none, por defecto none), fail-on-findings (obsoleto, por defecto false), include-tests (por defecto false), include-examples (por defecto false). Salida: sarif-file. La carga de SARIF requiere security-events: write y el escaneo de código habilitado.

El informe y la compuerta se construyen a partir de un único arreglo de argumentos, por lo que include-tests e include-examples se aplican a ambos — el SARIF que lees y el código de salida sobre el que aplicas la compuerta siempre describen el mismo conjunto de fuentes. La pasada de informes se ejecuta con --fail-on none para que los hallazgos nunca bloqueen la carga de SARIF, pero un fallo operativo (una ruta que no existe, un error de uso de la CLI) aún hace fallar el paso en lugar de reportarse como un escaneo limpio.

fail-on-findings: true se mantiene por compatibilidad y se asigna a fail-on: high cuando fail-on se deja en none; emite una advertencia de obsolescencia. Prefiere fail-on, que puede aplicar la compuerta en cualquier severidad.

Qué detecta (o1js)

Reglas compatibles de un vistazo

BackendReglasCapacidad altaCapacidad mediaCapacidad baja
o1js1811122
Noir11491
Total2915213

Los recuentos son IDs de reglas distintos compatibles con cada backend. Una regla que asigna severidad según el contexto (por ejemplo, alta para una transferencia de valor y media para una escritura de estado) aparece en más de una columna de severidad, por lo que las columnas de severidad intencionalmente no suman el total de reglas. Actualmente no hay reglas de severidad crítica ni informativa. Las descripciones completas y las protecciones contra falsos positivos se detallan a continuación.

ReglaSeveridadQué significa
O1JS_MISSING_STATE_PRECONDITIONaltaLectura de this.x.get() sin un requireEquals(...) / getAndRequireEquals() correspondiente. Un get() sin más no añade ninguna precondición de cuenta, por lo que la prueba no vincula x a su valor en cadena — un probador puede sustituir cualquier valor.
O1JS_UNCONSTRAINED_WITNESSalta / mediaUn argumento de @method (un testigo privado controlado por el probador) fluye hacia un monto de envío (this.send(...) o un AccountUpdate.create*(...).send(...) del mismo método) o un .set(...) de estado y nunca se asevera. Análogo directo de una señal Circom sub-restringida. Alta cuando alcanza una transferencia de valor.
O1JS_UNCONSTRAINED_PROVABLE_WITNESSalta / media / bajaUna variable local de Provable.witness(...) fluye hacia un efecto de envío/estado con ninguna aserción en circuito. El callback del testigo se ejecuta fuera del circuito (es solo una pista para el probador), por lo que el resultado es un valor fresco controlado por el probador — la otra fuente de testigos además de los argumentos de @method. Debe re-derivarse y aseverarse (x.assertEquals(<recomputed>)) o vincularse al estado. Alta en un monto de envío (this.send(...) o AccountUpdate.create* del mismo método).
O1JS_UNCONSTRAINED_RECIPIENTbajaUn argumento de @method se usa solo como el destinatario to: de un envío. Esto suele ser intencional (un usuario nombra su propio destino de retiro) y es informativo — solo importa si el destino está pensado para ser una tesorería fija o una dirección registrada en estado. No activa la compuerta del código de salida de CI.
O1JS_WITNESS_NOT_BOUND_TO_STATEmediaUn testigo solo está restringido trivialmente (p. ej. > 0, o comparado contra una constante) antes de un efecto — nunca vinculado al estado en cadena. Confirma que la orquestación fuera de cadena hace esto seguro, o el saldo es drenable hasta su valor permanente.

Protecciones contra falsos positivos (o1js)

El analizador está diseñado para mantenerse silencioso ante código correcto:

  • Los métodos con compuerta de firma se omiten. Un @method que llama a this.requireSignature() (o getAndRequireSignature, AccountUpdate.createSigned, Signature.verify) tiene compuerta de propietario/administrador — sus argumentos son elegidos por el titular de la clave, no por un probador arbitrario — por lo que sus testigos no se marcan. Este es el equivalente en o1js de onlyOwner.
  • Los testigos vinculados al estado se omiten. Un argumento aseverado igual a (o acotado por una comparación de orden contra) un valor derivado de getAndRequireEquals() es sólido y no se reportará. Esto cubre tanto la forma directa — amount.assertLessThanOrEqual(bal) — como la forma encadenada amount.lessThanOrEqual(bal).assertTrue(). La vinculación que reside en un helper no decorado de la misma clase (this.verifyX(arg)) también se reconoce, incluso a través de una cadena de tales helpers.
  • Las pruebas verificadas se omiten. Un argumento tipado como Proof / SelfProof / DynamicProof / *Proof sobre el que se llama a .verify() está restringido por el circuito verificado — los hallazgos de testigos sobre él (y su publicOutput / publicInput) se suprimen. Un .verifyIf(flag) se acredita solo cuando la condición no es un argumento de método sin restringir, o ella misma está aseverada. Lo mismo se aplica al envoltorio canónico OffchainState this.offchainState.settle(proof) (el framework verifica dentro de settle). Un .settle(proof) hecho a mano no se asume que verifica. El caso inverso (argumento tipado como prueba nunca verificado y no liquidado por OffchainState) se reporta como O1JS_UNVERIFIED_PROOF.
  • Los Bool aseverados / usados se omiten. Un predicado encadenado con .assertTrue() / .assertFalse(), anidado en Provable.if(...), o asignado a una variable local que luego se referencia, no se reporta como O1JS_UNASSERTED_BOOL.
  • Los remitentes autenticados se omiten. this.sender.getUnconstrained() no se activa cuando el mismo @method también llama a this.sender.getAndRequireSignature(), o cuando ese valor testificado se pasa a AccountUpdate.createSigned(...) / se autentica mediante .requireSignature() en un AccountUpdate construido a partir de él (se requiere identidad del argumento).
  • Los comentarios y literales de cadena se eliminan antes del análisis, por lo que un assert dentro de una cadena no puede crear un resultado falso.

Qué detecta (Noir)

La misma idea de solidez — testigos sub-restringidos — se aplica a Noir (circuitos .nr). Apunta el escáner a archivos .nr (o usa --lang noir) y los analiza con el conjunto de reglas de Noir. Mismo enfoque léxico, sin dependencias. Calibrado contra los modismos de oráculo / unsafe de aztec-nr — consulta docs/noir_calibration.md.

ReglaSeveridadQué significa
NOIR_UNCONSTRAINED_WITNESSaltaUn valor vinculado desde un bloque unsafe { ... } — el resultado de una unconstrained fn (pista de oráculo / Brillig) — que nunca se re-restringe mediante un assert / assert_eq (o un helper de confirmación / verificación de merkle). La pista se ejecuta fuera del circuito. Análogo de O1JS_UNCONSTRAINED_PROVABLE_WITNESS.
NOIR_UNCONSTRAINED_INPUTmediaUna entrada privada (testigo) de fn main que no fluye hacia ningún assert / assert_eq y no forma parte de la salida pública. Análogo de O1JS_UNCONSTRAINED_WITNESS.
NOIR_UNCONSTRAINED_PUBLIC_INPUTmediaUna entrada pública de fn main que no alcanza ninguna restricción ni salida — el circuito nunca la lee. El dual de la regla de testigo privado: el verificador suministra el valor y cree que la declaración es sobre él, mientras el circuito lo ignora (p. ej. un merkle_root: pub Field que nunca se verifica, por lo que la pertenencia nunca se probó realmente). MEDIA porque una entrada pública deliberadamente no usada también es un modismo legítimo para vincular una prueba a un contexto (nonce / id de cadena / destinatario), que es léxicamente indistinguible — por lo que no aplica la compuerta de CI con el --fail-on high por defecto.
NOIR_UNCHECKED_CASTmediaUn valor controlado por el probador convertido a un tipo sin signo estrecho (as u8/u16/u32) con ninguna aserción de rango. Análogo de MissingRangeCheck de o1js.
NOIR_UNCONSTRAINED_ARRAY_INDEXmediaUn valor controlado por el probador usado como índice de arreglo (arr[i]) con ninguna verificación de ningún tipo sobre él. La verificación implícita de límites de Noir establece solo que el índice está en rango — no que sea el índice correcto — por lo que el probador sigue siendo libre de seleccionar cualquier elemento y aún producir una prueba que verifica. Este es el bug de libertad de selector detrás de las posiciones de ruta de Merkle, la selección de notas y la pertenencia a listas de permitidos. Se suprime cuando el índice está acotado por rango, fijado por una igualdad, acotado antes de una conversión (), o cuando el valor leído de vuelta está a su vez fijado por un .

Protecciones contra falsos positivos (Noir)

  • Los helpers de assert / salto de let / confirmación en el mismo archivo vinculan las pistas unsafe.
  • Los nombres en el sitio de llamada constrain_* / confirm_* / verify_* / check_(non_)membership* / public_data_storage_read acreditan los argumentos (con detección de resultado no usado para verificaciones descartadas).
  • Sin restringir intencional documentado (requiere // Safety: adyacente): random(), avm::…, y redacción diferida de kernel/rollup/discovery.
  • let de tupla + flags aseverados vinculan testigos de merkle pasados a verificaciones de pertenencia.

Ejemplo:```console $ noir-scan examples/noir_unconstrained.nr --include-examples HIGH NOIR_UNCONSTRAINED_WITNESS noir_unconstrained.nr:16 fn=main Unconstrained unsafe result inv in main LOW NOIR_UNSAFE_MISSING_SAFETY noir_unconstrained.nr:16 fn= unsafe block without a // Safety: comment noir-scan: 2 finding(s) [1 high, 1 low] in 1 of 1 file(s) — fails (--fail-on high)

$ noir-scan examples/noir_constrained.nr --include-examples noir-scan: no findings in 1 o1js or Noir file(s) — passes (--fail-on high)

root@kitploit:~
Como en el ejemplo de o1js anterior, `--include-examples` solo es necesario porque
estos archivos de demostración se encuentran bajo `examples/`.

## Limitaciones conocidas

El analizador es un **frontend léxico sin dependencias más una capa semántica ligera** que
realiza seguimiento de alias y propagación interprocedural a través de helpers de la misma clase. No es un frontend del compilador de TypeScript, un verificador de tipos ni un motor de flujo de datos de programa completo, y no hay una capa SMT ni de prueba formal en este escáner.
Ten en cuenta estos puntos ciegos al realizar la triaje — son conocidos e intencionales
para este diseño sin dependencias, no errores:

- **Solo se siguen alias simples.** El seguimiento de testigos sigue alias simples
  del mismo método como `const q = qty`, pero no expresiones derivadas ni
  desestructuración:  ```ts
  const q = qty; this.send({ to: dest, amount: q });   // followed
  const q = qty.add(1); this.send({ to: dest, amount: q }); // not followed
  const slot = this.root; slot.get();                  // missing precondition missed
  • El binding entre métodos cubre únicamente cadenas de helpers de la misma clase. Un helper sin decorar de la misma clase invocado como this.verifyX(arg) puede enlazar por estado el argumento de un llamador, y desde 0.19.0 las cadenas de estos (@method → helper A → helper B) se siguen hasta un punto fijo. El paso helper→helper mapea solo una referencia de parámetro desnuda, por lo que helperA(x.add(1)) no se propaga. Las funciones libres e importadas siguen sin seguirse, y el aliasing de variables locales del argumento del helper sigue siendo una limitación documentada.

  • La detección de Bool no asertado tiene forma de sentencia. El Tier A solo marca sentencias de expresión desnudas cuya llamada más externa es un predicado Bool sin nada encadenado después. Los predicados anidados dentro de Provable.if(...), o asignados y usados posteriormente, no se marcan. Los usos complejos de flujo de control de un local Bool aún pueden pasarse por alto si el nombre nunca se referencia (modo de fallo: omisión, no un falso positivo).

  • El signature-gating es a nivel de método y basado en subcadenas. _method_is_signature_gated trata un @method completo como owner-gated si contiene un idioma de firma, y reconoce un verificador solo cuando el nombre del receptor contiene literalmente signature — por lo que sig.verify(admin, msg) no se reconoce como gating, mientras que una comprobación de firma no relacionada en otro lugar de un método grande puede sobresuprimir. Es todo o nada por método.

  • La autenticación del remitente se basa en el nombre y solo en el mismo método. O1JS_UNCONSTRAINED_SENDER suprime cuando this.sender.getAndRequireSignature() o AccountUpdate.createSigned(<that sender>) aparece en el mismo cuerpo de @method. Un requisito de firma que vive solo en un helper (this.requireSenderSig() → getAndRequireSignature dentro) no se sigue — el modo de fallo es un falso positivo en código correcto que envuelve el idioma, no un bug real omitido.

  • Los helpers cross-crate de Noir se reconocen solo por convención de nombres (sin resolución de Nargo.toml / imports). Preferir omisión sobre falso positivo.

Estas son la razón por la que los hallazgos son un punto de partida para la revisión humana, no pruebas. Una reescritura consciente del flujo de datos está deliberadamente fuera del alcance del analizador léxico.

Dónde se detiene esta herramienta

o1js-scan es deliberadamente un paso léxico superficial de un solo archivo — sin parser, sin flujo de datos, sin solver. Eso es lo que lo hace libre de dependencias e instantáneo en CI, y también es un techo duro. Las limitaciones anteriores no son un backlog; son consecuencias del diseño.

Por lo tanto, vale la pena ser explícito sobre lo que esta herramienta puede y no puede decirte:

  • Una ejecución limpia no es una auditoría. Significa que ninguna forma que este escáner reconoce coincidió — no que el circuito sea sólido. Las clases de bugs que necesitan flujo de datos, sensibilidad a rutas o resolución de restricciones están fuera del alcance de una herramienta de esta forma, en cualquier lenguaje.
  • Un hallazgo es una pista, no un veredicto. Cada regla aquí es una heurística con una clase de falso positivo documentada.

Ese intercambio es el correcto para un linter que ejecutas en cada commit. Si estás trabajando en algo donde la diferencia importa — un protocolo que contiene valor real, un circuito que no puedes permitirte equivocar — trátalo como la primera pasada y presupuesta para una revisión real.

Para un análisis más profundo, el escáner completo separado se mantiene en el repositorio audit-engine-cli. o1js-scan es el escáner abierto intencionalmente ligero; el conocimiento de detección propietario y los detalles de implementación del escáner completo no se reproducen aquí. Para acceso o una revisión de circuito más completa, contacta: [email protected].

Privacidad y código privado

El CLI instalado analiza archivos localmente. No tiene telemetría, cliente de red, cuenta ni paso de subida, y su runtime de Python no tiene dependencias de terceros. Ejecutar o1js-scan path/to/private-repo no envía el código fuente ni los hallazgos a ningún lugar.

Como los logs del compilador, la salida del escáner puede contener rutas, identificadores y fragmentos de código fuente. SARIF también identifica ubicaciones exactas del repositorio, y la GitHub Action lo sube al code scanning de GitHub. Usa los mismos controles de acceso de repositorio y CI que ya usas para el código fuente que se está escaneando.

¿Quieres contribuir con un informe útil de falso positivo o detección omitida sin compartir una aplicación? Reproduce la sintaxis con nombres y constantes inventados, elimina la lógica de negocio una sentencia a la vez, y verifica que el fragmento sintético sigue disparando la misma regla antes de publicarlo. La guía de contribución segura para la privacidad tiene una checklist concreta y varias formas de ayudar a la comunidad o1js sin divulgar un circuito privado.

Este límite no impide que el escáner abierto mejore. La documentación pública de o1js y los repositorios pueden respaldar nuevas reglas y fixtures de compatibilidad; los ejemplos sintéticos pueden probar falsos positivos y restricciones omitidas; y la resiliencia del parser, los diagnósticos, SARIF, el rendimiento, el empaquetado y la calibración pueden mejorar sin publicar una técnica de auditoría privada o código de cliente. El escáner abierto debe hacer afirmaciones explicables de forma independiente; la investigación privada puede permanecer en el motor de auditoría separado.

Revisión post-cuántica

El riesgo cuántico está relacionado con la seguridad del circuito, pero no es una regla de restricción faltante. o1js-scan no determina si una firma, hash, commitment, el sistema de pruebas Kimchi o Mina mismo cumple un objetivo de seguridad post-cuántica. Esas respuestas dependen de la primitiva y los parámetros concretos, los supuestos de la plataforma, el tiempo de vida requerido del despliegue y su plan de migración — no meramente de un identificador de TypeScript que un escáner léxico puede ver.

Inspirado por Qubit or Not Qubit de O(1) Labs, la guía de revisión post-cuántica convierte ese límite en un inventario específico de o1js y una checklist de agilidad criptográfica. Úsala junto con este escáner en lugar de interpretar un escaneo limpio como una evaluación post-cuántica.

Compatibilidad

Funciona en o1js 1.x, 2.x y 3.x, incluido el hard fork Mesa al que apunta o1js 3.0.0. o1js-scan analiza el código fuente de TypeScript como texto y no tiene dependencia en tiempo de ejecución de o1js — nada está fijado a una versión. Se basa en la API moderna de precondiciones require* (getAndRequireEquals, requireEquals, requireSignature, getAndRequireSignature), los decoradores @method / @method() / @method.returns(...), los campos @state anotados, this.send({...}), las transferencias de bajo nivel AccountUpdate.balance.subInPlace(...) y Permissions.*. Las formas establecidas siguen siendo compatibles a través de los límites 1.x → 2.x → 3.x, mientras que el escáner también acepta las variantes de decorador y transferencia de bajo nivel recientemente documentadas. El idioma de autenticación de propietario de 2.x this.sender.getAndRequireSignature() se reconoce como signature-gating. (Las precondiciones heredadas assertEquals todavía se aceptan, así que el código más antiguo tampoco se rompe.)

Los cambios disruptivos de Mesa son todos a nivel de runtime y protocolo — la eliminación de Transaction.setFeePerSnarkCost() y las constantes TransactionCost.*, la nueva forma de VerificationKey.toJSON(), las claves de verificación regeneradas, MAX_ZKAPP_STATE_FIELDS elevado de 8 a 32, y el formato de transacción de mina-signer v4. Ninguno de ellos renombra una API que este escáner detecte, por lo que ninguna regla cambió para Mesa, y eso está verificado en lugar de afirmado. scripts/o1js_release_matrix.sh escanea dos releases de o1js fijados que abarcan el límite del protocolo — 2.15.0 (9620ef08, el último release de 2.x) y 3.0.0 (cc18a919, Mesa) — y compara cada hallazgo contra tests/fixtures/o1js_release_matrix.json:

ReleaseHallazgosHIGHMEDIUMLOWArchivos
o1js 2.15.036826218
o1js 3.0.0 (Mesa)39829219

33 hallazgos son idénticos a través del límite, ninguno se perdió, y los tres nuevos están en src/examples/zkapps/big-state-zkapp.ts — el ejemplo de 32 campos de estado que existe solo porque Mesa elevó MAX_ZKAPP_STATE_FIELDS. Ese delta está fijado por un test, por lo que no puede desviarse silenciosamente. La matriz se ejecuta en cada build de CI; el job semanal o1js-upstream-canary además rastrea o1js en HEAD, por delante de cualquier release.

Las grafías equivalentes de restricciones se normalizan para el análisis: instancia assertEquals(...), estático Provable.assertEqual(Type, ...), y cadenas de igualdad equals(...).assertTrue() todas enlazan los mismos operandos. La extracción de métodos está balanceada por llaves tras el enmascaramiento de comentarios y cadenas que preserva la longitud, y acepta decoradores multilínea, tipos de parámetros con forma de callback anidados, modificadores de acceso de TypeScript y alias de identidad multilínea (incluyendo formas entre paréntesis y as Type).

El análisis de Noir apunta a la sintaxis de Noir usada por proyectos Aztec / nargo (.nr); no invoca nargo ni compila circuitos.

Cómo funciona

Es un analizador léxico, no un parser completo de TypeScript o Noir — las fuentes de o1js y Noir están delimitadas por llaves y son tratables con regex, y la salida está pensada para ser triada por un humano. Eso lo mantiene libre de dependencias e instantáneo de ejecutar en CI. Los hallazgos son un punto de partida para la revisión, no pruebas.

Roadmap / contribuir

Contribuciones bienvenidas — nuevas familias de reglas, más guardas de FP y arquetipos de calibración del mundo real son todos valiosos. Consulta CONTRIBUTING.md.

Para una ruta propuesta desde el listado de Community Packages hasta una comprobación de aviso en el repositorio de o1js, consulta la propuesta de integración upstream de o1js lista para enviar.

Ejecuta los tests y el linter con:```bash pip install -e ".[dev]" pytest # unit tests + Noir/o1js corpus ruff check . # lint npm run format:check # prettier, npm wrapper only

root@kitploit:~
## Licencia

Apache-2.0. Ver [`LICENSE`](https://github.com/auditinfra-io/o1js-scan/blob/main/LICENSE).
Descargar herramienta
O1JS_STALE_MERKLE_ROOTaltaUn método recalcula una raíz de Merkle a partir de un testigo proporcionado por el probador (computeRootAndKey / calculateRoot) pero no vincula ninguna de las raíces recalculadas a la raíz actual en cadena. Sin un this.root.requireEquals(...) / assertEquals contra la raíz viva, un probador puede pasar un testigo para un árbol fabricado u obsoleto — forjando pertenencia o reproduciendo estado antiguo. La vinculación puede residir en un helper no decorado de la misma clase (this.verifyX(witness)); la propagación de helpers cubre eso.
O1JS_UNVERIFIED_PROOFaltaUn parámetro de @method tipado como Proof<...> / SelfProof<...> / DynamicProof<...> nunca se .verify() antes de que se usen sus campos públicos. Pasar una Proof no la verifica — sin una verificación explícita el probador puede suministrar un objeto de prueba arbitrario, y cualquier uso de su publicOutput queda sin restringir. También se activa cuando .verifyIf(flag) está condicionado por un argumento de @method sin restringir y se leen los campos públicos de la prueba, porque el probador puede hacer que la condición sea falsa.
O1JS_UNASSERTED_BOOLalta / mediaUn predicado de o1js (equals / lessThanOrEqual / …) devuelve un Bool y no añade ninguna restricción a menos que el resultado se asevere o se use. ALTA cuando la llamada es una sentencia desechada sin más; MEDIA cuando se asigna a una variable local que nunca se vuelve a referenciar.
O1JS_UNCONSTRAINED_SENDERalta / mediathis.sender.getUnconstrained() devuelve el remitente de la tx sin probarlo. ALTA cuando ese valor (o una variable local derivada de él) fluye hacia un assert / .set de estado / send (verificación vacua); MEDIA en caso contrario. Prefiere this.sender.getAndRequireSignature(), o el modismo expandido AccountUpdate.createSigned(sender). Se mantiene silenciosa cuando (1) el mismo @method también llama a this.sender.getAndRequireSignature() en cualquier lugar (el requisito de firma tiene alcance de método), o (2) el valor del remitente testificado es el argumento de AccountUpdate.createSigned(...) / un AccountUpdate.create(...).requireSignature() sobre esa misma clave (se requiere identidad del argumento — un createSigned sobre una clave diferente no lo suprime).
MissingRangeCheckaltaUn Field sin procesar (no el UInt64/UInt32 con verificación de rango) se usa como monto de transferencia. Un Field es un elemento mód p y no está acotado por rango.
O1JS_WEAK_PERMISSIONSalta / mediaeditState / send establecidos en proofOrSignature() o none(), permitiendo que la clave de la cuenta zkApp evite el circuito firmando. También marca setVerificationKey / setPermissions dejados en signature / proofOrSignature / none (las ruedas de entrenamiento de actualización documentadas de Mina); ALTA cuando se combina con un editState/send débil en el mismo permissions.set.
O1JS_LOGIC_OUTSIDE_PROOFaltaLógica de seguridad (assert / approve / send / .set de estado) dentro de Provable.asProver(...) o un callback de Provable.witness*. Esos callbacks se ejecutan fuera del circuito — un probador malicioso puede eliminarlos y aún producir una prueba que verifica.
O1JS_APPROVE_WITHOUT_BINDINGmediaUn @method llama a approve / approveAccountUpdate / approveBase sin leer balanceChange / publicKey y sin assertCanMint / assertCanBurn / una verificación de conservación forEachUpdate — el arquetipo de Mina FlawedTokenContract.
O1JS_VACUOUS_ASSERTalta / mediaUn assert que se satisface por construcción: x.assertEquals(x), x.equals(x).assertTrue(), o Bool(true).assertTrue(). ALTA para autocomparaciones (casi siempre un error tipográfico); MEDIA para asserts de Bool constante.
O1JS_CONDITIONAL_ASSERTmediaUn assert dentro de if <flag> { ... } donde <flag> es un Bool de @method controlado por el probador (o una variable local de .toBoolean()). Un condicional de JS no restringe el circuito como lo hace Provable.if. Las comparaciones en línea se dejan sin reportar por precisión.
O1JS_GUARDED_INVERSEmediaUn .div() / .inv() / .sqrt() dentro de una rama de Provable.if, protegido por una condición sobre el mismísimo valor con el que falla. Ambas ramas se evalúan en circuito y estas llamadas aseveran incondicionalmente que la inversa o la raíz existe, por lo que la guarda no omite la aserción — el circuito es insatisfacible exactamente para la entrada que la guarda fue escrita para manejar, y el método nunca puede probarse para ella. Reportado por Veridise como V-O1J-VUL-060. Calcula primero un divisor seguro (Provable.if(isZero, Field(1), d)) y selecciona el resultado después. Se mantiene silenciosa cuando la guarda no dice nada sobre el divisor, por lo que un Provable.if no relacionado alrededor de una división segura no se marca.
O1JS_PRECONDITION_OVERWRITTENmediaDos o más llamadas a requireEquals / requireBetween / requireNothing sobre la misma propiedad en un método, con argumentos diferentes. Las precondiciones se establecen en el AccountUpdate en lugar de acumularse, por lo que cada llamada sobrescribe la anterior y solo se aplica la última — a diferencia de las aserciones en circuito, que se componen. a.requireEquals(b) y luego a.requireEquals(c) implica a === c, no a === b. Reportado por Veridise como V-O1J-VUL-012. Se mantiene silenciosa cuando los argumentos son idénticos (idempotente, nada se pierde), en getAndRequireEquals() (un método diferente, por lo que las lecturas de estado repetidas están bien), y cuando las llamadas se sitúan en ramas de JS mutuamente excluyentes, que se resuelven en tiempo de construcción del circuito. Esa última exención puede ocultar una sobrescritura real que cruza un if/else no relacionado.
O1JS_STATE_READ_AFTER_WRITEmediaUn campo @state se lee (get() / getAndRequireEquals()) después de que se completa un set(...) sobre el mismo campo, en el mismo método. set() registra el cambio en el AccountUpdate pero no escribe a través de get(), por lo que la lectura aún observa el valor de antes de la escritura y cualquier aritmética construida sobre él está silenciosamente desviada por esa escritura. Reportado por Veridise como V-O1J-VUL-030. Mantén el nuevo valor en una variable local en lugar de volver a leer el estado. Se mantiene silenciosa cuando la lectura está anidada dentro de los propios argumentos de la escritura (el modismo de lectura-modificación-escritura this.x.set(this.x.getAndRequireEquals().add(1)), que es correcto), y cuando la escritura y la lectura se sitúan en ramas de JS mutuamente excluyentes. Con alcance a un solo método — el caso de caché entre métodos que Veridise también describe necesita conocimiento del grafo de llamadas que esta regla no tiene.
index.assert_max_bit_size::<8>(); let i = index as u32;
assert_eq
NOIR_UNASSERTED_BOOLalta / mediaUna comparación cuyo resultado bool se descarta. Análogo de O1JS_UNASSERTED_BOOL de o1js.
NOIR_CONDITIONAL_ASSERTmediaUn assert dentro de if <flag> { ... } donde <flag> es un bool sin más controlado por el probador o una variable local derivada de valores controlados por el probador. Una restricción dentro de un condicional solo se aplica cuando la condición es verdadera, por lo que una rama elegida por el probador puede omitir la verificación. Las comparaciones en línea (if x != 0) se dejan intactas por precisión; asignar la guarda a una variable local (let gate = x != 0; if gate) se reporta a menos que gate esté a su vez aseverada.
NOIR_CONDITIONAL_CONSTRAINmediaUna llamada a constrain_* / confirm_* / verify_* solo bajo un if controlado por el probador, mientras una pista unsafe aún alcanza la salida.
NOIR_UNUSED_CHECK_RESULTalta / mediaUn resultado de check_* / confirm_* / verify_* / constrain_* se descarta (llamada sin más) o se asigna y nunca se asevera — la verificación no vincula el circuito.
NOIR_VACUOUS_CONSTRAINTalta / mediaUna restricción que se satisface por construcción: una autocomparación (assert(x == x), assert_eq(x, x), x >= x) o una condición constante (assert(true)). No añade ninguna restricción, pero la línea se lee como una verificación — lo que la hace más peligrosa que una restricción ausente, porque la revisión se detiene ahí. ALTA para una autocomparación (casi siempre un error tipográfico por una verificación real: assert(computed == expected) mal escrito como assert(expected == expected)); MEDIA para una constante, que más a menudo es un marcador de posición. x != x no se marca — eso es insatisfacible, un bug de vivacidad más que un agujero de solidez silencioso.
NOIR_UNSAFE_MISSING_SAFETYbajaUn bloque unsafe { ... } sin un comentario // Safety: adyacente. Informativo; no hace fallar CI con el --fail-on high por defecto.