Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
Ferramentas/GitHubGitHub/auditinfra-io/o1js-scan
Ferramentas DefensivasAnálise EstáticaScanners de VulnerabilidadesAnálise Estática de Código (SAST)Análise de VulnerabilidadesAnálise de CódigoCriptografiaDevSecOps
GitHubauditinfra-io/o1js-scan

o1js-scan

Analisador estático sem dependências para bugs de solidez em circuitos zk em zkApps o1js/Mina e circuitos Noir

Ver Repositório
210há 4 diasAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Site
Compartilhar

o1js-scan

CI Python License PyPI npm

Pacote da comunidade: o1js-scan está listado no diretório oficial o1js Community Packages.

Mais recente: 0.20.0 — o analisador agora lê contratos que extends TokenContract. Até esta versão, o filtro de contratos correspondia apenas a SmartContract, então todo token fungível, coleção NFT e pool AMM do ecossistema era escaneado como "sem achados". Se você escaneou um contrato de token antes da 0.20.0, escaneie novamente. Veja o .

CHANGELOG

Um analisador estático rápido e sem dependências para bugs de solidez de circuitos zk em:

  • o1js / Mina zkApps (TypeScript .ts / .js) — circuitos Kimchi a partir de corpos de @method
  • Noir (.nr) — a DSL ZK de estilo Rust da Aztec (incluindo padrões no formato aztec-nr)

Os bugs críticos de segurança geralmente não estão no sistema de prova — eles estão nas próprias restrições da aplicação: witnesses que o provador controla, mas que o circuito nunca vincula. o1js-scan é o scanner de sinais sub-restritos para os primos do Circom nos ecossistemas Mina e 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:~
### Exemplo

Dado um cofre cujo valor de `withdraw` é uma testemunha controlada pelo provador que
nunca é vinculada ao 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 é necessário aqui apenas porque o arquivo de demonstração está em examples/, que o classificador de caminhos rebaixa por padrão para que o próprio código de exemplo de um repositório não possa falhar a sua build. O mesmo contrato no seu src/ reporta HIGH sem nenhuma flag.

O achado HIGH é o bug drenável. O contrato corrigido (examples/safe_vault.ts) elimina-o e sai com 0, mantendo apenas o LOW informativo sobre o destinatário escolhido pelo prover:```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:~
Consulte [`examples/`](https://github.com/auditinfra-io/o1js-scan/blob/main/examples) para os pares vulneráveis/corrigidos de o1js e Noir.

## Conteúdo

- [Instalação](#install)
- [Uso](#usage) · [Suprimindo um achado](#suppressing-a-reviewed-finding)
- [GitHub Action](#github-action)
- [O que ele detecta — o1js](#what-it-detects-o1js) · [Noir](#what-it-detects-noir)
- [Limitações conhecidas](#known-limitations) · [Onde esta ferramenta para](#where-this-tool-stops)
- [Privacidade e código privado](#privacy-and-private-code)
- [Revisão pós-quântica](#post-quantum-review)
- [Compatibilidade](#compatibility) · [Como funciona](#how-it-works)
- [Contribuindo](#roadmap--contributing)

## Instalação```bash
pip install o1js-scan

Para uma instalação global isolada da CLI, use pipx:```bash pipx install o1js-scan

root@kitploit:~
Para repositórios de aplicações Noir, Aztec ou o1js baseados em Node/npm, instale o wrapper npm:```bash
npm install -D o1js-scan
npx noir-scan . --lang noir --fail-on high

O pacote npm é um wrapper fino em torno do mesmo analisador Python e requer Python 3.8+ no PATH (python3 ou python). Defina O1JS_SCAN_PYTHON para escolher um interpretador específico.

Ou a partir do código-fonte:```bash git clone https://github.com/auditinfra-io/o1js-scan cd o1js-scan pip install -e .

root@kitploit:~
Sem dependências Python de terceiros. Python 3.8+. O script de console `noir-scan` é
instalado juntamente com `o1js-scan` (mesmo ponto de entrada), inclusive através do wrapper
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

O código de saída é 1 quando um achado no nível --fail-on ou acima (padrão high) está presente e 0 caso contrário — assim você pode inseri-lo diretamente no CI. Com o padrão, um achado de baixa/média severidade (incluindo a regra informativa de destinatário abaixo) não falha a build; use --fail-on none para apenas reportar, ou --strict (um atalho para --fail-on medium) para restringir mais estritamente enquanto ainda trata achados de baixa severidade como informativos. As duas opções são mutuamente exclusivas para que a configuração de CI não possa ser ambígua. Um caminho de scan ausente sai com 2 com um erro em stderr, então um erro de digitação não pode passar silenciosamente no CI como uma execução limpa. Cada execução imprime um resumo de uma linha (contagens por severidade e o veredito do gate) em stderr.

Código de teste é excluído por padrão — ambos os backends. Testes deliberadamente constroem valores inválidos e transações ruins para provar que os asserts os rejeitam, então um achado ali é o propósito do teste e não um bug de circuito. Um arquivo conta como código de teste quando:

  • seu nome corresponde a *.test.ts / *.spec.ts (e as variantes .js/.jsx/.tsx/.mjs /.cjs), ou *_test.nr / test_*.nr;
  • está sob um diretório test/, tests/, __tests__/, spec/ ou __mocks__/;
  • (apenas Noir, baseado em conteúdo) a função carrega um atributo #[test] / #[test(...)], ou está dentro de um bloco mod test { … } / mod tests { … } — com escopo de bloco, então um módulo de teste no final de um arquivo de produção não silencia o resto dele.

Passe --include-tests para reportá-los.

Código de exemplo é rebaixado, não descartado. Um achado em um diretório examples/ ou example/, ou em um arquivo chamado *.eg.ts (.nr e as outras extensões JS/TS também), é rebaixado para LOW com uma nota — ainda reportado, não mais capaz de falhar uma build. Código de exemplo é deliberadamente simplificado, e sinalizar os próprios exemplos de um framework como vulnerabilidades é ruído; mas ele é copiado para produção com muito mais frequência do que código de teste, e é por isso que é rebaixado em vez de ocultado. Passe --include-examples para manter a severidade original.

Sempre que qualquer uma das políticas se aplica, a execução imprime uma linha em stderr dizendo isso — por exemplo, 6 file(s) skipped as test code, 1 finding(s) downgraded as examples — para que um scan silencioso nunca seja silenciosamente silencioso. As contagens também aparecem no SARIF sob invocation.properties. Note o trade-off: a detecção é apenas baseada em caminho (sem parsing de describe(/it(), então um circuito de produção armazenado sob tests/ será ignorado — a linha de stderr é como você percebe.

Diretórios ignorados ao percorrer uma árvore: node_modules, target (nargo), .git, dist, build, __pycache__, .venv, venv.

Suprimindo um achado revisado

Silencie um achado que você triou sem afrouxar o gate, com um comentário inline na — ou na linha acima da — linha sinalizada:```ts this.send({ to, amount }); // o1js-scan-disable-line O1JS_UNCONSTRAINED_WITNESS

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

root@kitploit:~
| `--no-color` | Desativa a saída colorida |
| `--debug` | Ativa a saída de depuração |
| `--verbose` | Ativa a saída detalhada |
| `--version` | Exibe a versão do programa |
| `--help` | Exibe a mensagem de ajuda |

### Exemplos de Uso

```bash
# Escanear um único repositório
python3 GitDorker.py -tf tokens.txt -q "example" -d repos.txt -o results.txt

# Escanear uma organização
python3 GitDorker.py -tf tokens.txt -q "example" -org "myorg" -o results.txt

# Escanear um usuário
python3 GitDorker.py -tf tokens.txt -q "example" -u "myuser" -o results.txt

# Escanear um único repositório
python3 GitDorker.py -tf tokens.txt -q "example" -r "https://github.com/user/repo" -o results.txt

Solução de Problemas

Problemas Comuns

  1. Nenhum token válido encontrado

    • Verifique se seus tokens do GitHub estão corretos
    • Certifique-se de que os tokens tenham os escopos necessários
    • Verifique se os tokens não expiraram
  2. Limite de taxa excedido

    • Adicione mais tokens
    • Reduza a taxa de requisições
    • Aguarde antes de tentar novamente
  3. Nenhum resultado encontrado

    • Verifique sua consulta de pesquisa
    • Tente termos de pesquisa diferentes
    • Certifique-se de que o repositório/organização/usuário existe

Modo de Depuração

Ative a saída de depuração para solucionar problemas:

root@kitploit:~
python3 GitDorker.py -tf tokens.txt -q "example" -d repos.txt --debug

Aviso Legal

Esta ferramenta é apenas para fins educacionais e de pesquisa. Os usuários são responsáveis por garantir que suas ações estejam em conformidade com todas as leis e regulamentos aplicáveis. Os autores não se responsabilizam por qualquer uso indevido ou dano causado por esta ferramenta.

Licença

Este projeto está licenciado sob a Licença MIT - consulte o arquivo LICENSE para obter detalhes.

Agradecimentos

  • Agradecimentos especiais a todos os contribuidores
  • Inspirado por várias ferramentas e técnicas de OSINT
  • Construído com PyGithub

Aviso

Este projeto está em desenvolvimento ativo. Os recursos e a API podem mudar sem aviso prévio.

Contato

Para dúvidas ou preocupações, por favor abra uma issue no repositório do GitHub.


Aviso: Esta ferramenta deve ser usada apenas para fins educacionais e de pesquisa. Sempre obtenha a devida autorização antes de realizar qualquer teste de segurança.```nr let inv = unsafe { hint(x) }; // o1js-scan-disable-line NOIR_UNCONSTRAINED_WITNESS

root@kitploit:~
Liste um ou mais ids de regras para suprimir apenas essas; uma diretiva isolada (sem ids)
suprime todas as regras na linha de destino.

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

Adicione o scanner ao CI em poucas linhas. Os achados aparecem como anotações no diff do PR e como alertas na aba Security → Code scanning do repositório.```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:~
### Receita de CI somente para Noir

Recomendada para projetos Noir que desejam alertas de varredura de código e um
gate de alta severidade:```yaml
- uses: auditinfra-io/[email protected]
  with:
    path: .
    lang: noir
    fail-on: high

Ou sem a Action:```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 (padrão .), lang (auto|o1js|noir, padrão auto), version (versão do PyPI a instalar, padrão a mais recente), upload-sarif (padrão true), fail-on (critical|high|medium|low|none, padrão none), fail-on-findings (obsoleto, padrão false), include-tests (padrão false), include-examples (padrão false). Saída: sarif-file. O upload do SARIF requer security-events: write e a verificação de código (code scanning) ativada.

O relatório e o gate são construídos a partir de um único array de argumentos, pelo que include-tests e include-examples se aplicam a ambos — o SARIF que lê e o código de saída sobre o qual faz o gate descrevem sempre o mesmo conjunto de fontes. A passagem de relatório corre com --fail-on none para que os achados nunca bloqueiem o upload do SARIF, mas uma falha operacional (um caminho que não existe, um erro de utilização da CLI) continua a falhar o passo em vez de ser reportada como uma análise limpa.

fail-on-findings: true é mantido por compatibilidade e mapeia para fail-on: high quando fail-on é deixado em none; emite um aviso de depreciação. Prefira fail-on, que pode fazer o gate em qualquer severidade.

O que deteta (o1js)

Regras suportadas num relance

BackendRegrasCapaz de HighCapaz de MediumCapaz de Low
o1js1811122
Noir11491
Total2915213

As contagens são IDs de regras distintos suportados por cada backend. Uma regra que atribui severidade de acordo com o contexto (por exemplo, high para uma transferência de valor e medium para uma escrita de estado) aparece em mais do que uma coluna de severidade, pelo que as colunas de severidade intencionalmente não somam o total de regras. Atualmente não existem regras de severidade critical ou info. As descrições completas e as proteções contra falsos positivos seguem-se abaixo.

RegraSeveridadeO que significa
O1JS_MISSING_STATE_PRECONDITIONhighLeitura de this.x.get() sem um requireEquals(...) / getAndRequireEquals() correspondente. Um get() simples não adiciona nenhuma pré-condição de conta, pelo que a prova não vincula x ao seu valor on-chain — um provador pode substituir qualquer valor.
O1JS_UNCONSTRAINED_WITNESShigh / mediumUm argumento de @method (uma testemunha privada controlada pelo provador) flui para um montante de envio (this.send(...) ou um AccountUpdate.create*(...).send(...) do mesmo método) ou para um .set(...) de estado e nunca é asseverado. Análogo direto de um sinal Circom sub-restrito. High quando atinge uma transferência de valor.
O1JS_UNCONSTRAINED_PROVABLE_WITNESShigh / medium / lowUma variável local de Provable.witness(...) flui para um efeito de envio/estado com nenhuma asserção in-circuit. O callback da testemunha corre fora do circuito (é apenas uma dica para o provador), pelo que o resultado é um valor novo controlado pelo provador — a outra fonte de testemunhas além dos argumentos de @method. Tem de ser re-derivado e asseverado (x.assertEquals(<recomputed>)) ou vinculado ao estado. High num montante de envio (this.send(...) ou AccountUpdate.create* do mesmo método).
O1JS_UNCONSTRAINED_RECIPIENTlowUm argumento de @method é usado apenas como o destinatário to: de um envio. Isto é habitualmente intencional (um utilizador nomeia o seu próprio destino de levantamento) e é informativo — só importa se o destino se destinar a ser um tesouro fixo ou um endereço registado em estado. Não aciona o gate do código de saída da CI.
O1JS_WITNESS_NOT_BOUND_TO_STATEmediumUma testemunha é apenas trivialmente restringida (por exemplo, > 0, ou comparada com uma constante) antes de um efeito — nunca ligada ao estado on-chain. Confirme que a orquestração off-chain torna isto seguro, ou o saldo é drenável até ao seu valor permanente.

Proteções contra falsos positivos (o1js)

O analisador foi concebido para se manter silencioso em código correto:

  • Métodos com gate de assinatura são ignorados. Um @method que chama this.requireSignature() (ou getAndRequireSignature, AccountUpdate.createSigned, Signature.verify) tem gate de proprietário/admin — os seus argumentos são escolhidos pelo detentor da chave, não por um provador arbitrário — pelo que as suas testemunhas não são sinalizadas. Este é o equivalente o1js de onlyOwner.
  • Testemunhas vinculadas ao estado são ignoradas. Um argumento asseverado como igual a (ou limitado por uma comparação de ordenação contra) um valor derivado de getAndRequireEquals() é sólido e não será reportado. Isto cobre tanto a forma direta — amount.assertLessThanOrEqual(bal) — como a forma encadeada amount.lessThanOrEqual(bal).assertTrue(). A vinculação que reside num helper não decorado da mesma classe (this.verifyX(arg)) também é reconhecida, incluindo através de uma cadeia de tais helpers.
  • Provas verificadas são ignoradas. Um argumento tipado como Proof / SelfProof / DynamicProof / *Proof sobre o qual .verify() é chamado é restringido pelo circuito verificado — os achados de testemunha sobre ele (e o seu publicOutput / publicInput) são suprimidos. Um .verifyIf(flag) só é creditado quando a condição não é um argumento de método sem restrições, ou é ela própria asseverada. O mesmo se aplica ao wrapper canónico OffchainState this.offchainState.settle(proof) (o framework verifica dentro de settle). Um .settle(proof) feito à mão não é assumido como verificador. O caso inverso (argumento tipado como prova nunca verificado e não liquidado via OffchainState) é reportado como O1JS_UNVERIFIED_PROOF.
  • Bools asseverados / usados são ignorados. Um predicado encadeado com .assertTrue() / .assertFalse(), aninhado em Provable.if(...), ou atribuído a uma variável local que é posteriormente referenciada, não é reportado como O1JS_UNASSERTED_BOOL.
  • Remetentes autenticados são ignorados. this.sender.getUnconstrained() não dispara quando o mesmo @method também chama this.sender.getAndRequireSignature(), ou quando esse valor testemunhado é passado a AccountUpdate.createSigned(...) / autenticado via .requireSignature() num AccountUpdate construído a partir dele (é exigida identidade do argumento).
  • Comentários e literais de string são removidos antes da análise, pelo que um assert dentro de uma string não pode criar um resultado falso.

O que deteta (Noir)

A mesma ideia de solidez — testemunhas sub-restritas — aplica-se aos circuitos Noir (.nr). Aponte o scanner a ficheiros .nr (ou use --lang noir) e ele analisa-os com o conjunto de regras Noir. Mesma abordagem lexical, sem dependências. Calibrado contra idiomas de oráculo / unsafe do aztec-nr — ver docs/noir_calibration.md.

RegraSeveridadeO que significa
NOIR_UNCONSTRAINED_WITNESShighUm valor vinculado a partir de um bloco unsafe { ... } — o resultado de uma unconstrained fn (oráculo / dica Brillig) — que nunca é re-restringido por um assert / assert_eq (ou um helper de confirmação / verificação de merkle). A dica corre fora do circuito. Análogo de O1JS_UNCONSTRAINED_PROVABLE_WITNESS.
NOIR_UNCONSTRAINED_INPUTmediumUma entrada privada (testemunha) de fn main que não flui para nenhum assert / assert_eq e não faz parte da saída pública. Análogo de O1JS_UNCONSTRAINED_WITNESS.
NOIR_UNCONSTRAINED_PUBLIC_INPUTmediumUma entrada pública de fn main que não atinge nenhuma restrição nem nenhuma saída — o circuito nunca a lê. O dual da regra da testemunha privada: o verificador fornece o valor e acredita que a afirmação é sobre ele, enquanto o circuito o ignora (por exemplo, um merkle_root: pub Field que nunca é verificado, pelo que a pertença nunca foi realmente provada). MEDIUM porque uma entrada pública deliberadamente não usada é também um idioma legítimo para vincular uma prova a um contexto (nonce / chain id / destinatário), que é lexicalmente indistinguível — pelo que não faz o gate da CI no --fail-on high padrão.
NOIR_UNCHECKED_CASTmediumUm valor controlado pelo provador convertido para um tipo unsigned estreito (as u8/u16/u32) com nenhuma asserção de intervalo. Análogo de MissingRangeCheck do o1js.
NOIR_UNCONSTRAINED_ARRAY_INDEXmediumUm valor controlado pelo provador usado como índice de array (arr[i]) com nenhuma verificação de qualquer tipo sobre ele. A verificação implícita de limites do Noir estabelece apenas que o índice está dentro do intervalo — não que é o índice correto — pelo que o provador continua livre de selecionar qualquer elemento e ainda produzir uma prova que verifica. Este é o bug de liberdade de seletor por trás das posições de caminho de Merkle, seleção de notas e pertença a allow-list. Suprimido quando o índice tem limite de intervalo, é fixado por uma igualdade, é limitado antes de uma conversão (), ou quando o valor lido de volta é ele próprio fixado por um .

Proteções contra falsos positivos (Noir)

  • Helpers de assert / let-hop / confirmação no mesmo ficheiro vinculam dicas unsafe.
  • Nomes de call-site constrain_* / confirm_* / verify_* / check_(non_)membership* / public_data_storage_read creditam argumentos (com deteção de resultado não usado para verificações descartadas).
  • Unconstrained intencional documentado (requer // Safety: adjacente): random(), avm::…, e formulação diferida de kernel/rollup/discovery.
  • let de tuplo + flags asseveradas vinculam testemunhas de merkle passadas para verificações de pertença.

Exemplo:```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 no exemplo o1js acima, `--include-examples` só é necessário porque
esses arquivos de demonstração ficam em `examples/`.

## Limitações conhecidas

O analisador é um **frontend lexical sem dependências mais uma camada semântica leve** que faz rastreamento de alias e propagação interprocedural através de helpers da mesma classe. Ele não é um frontend do compilador TypeScript, um verificador de tipos ou um motor de fluxo de dados de programa inteiro, e não há camada de SMT ou prova formal neste scanner.
Tenha esses pontos cegos em mente ao fazer a triagem — eles são conhecidos e intencionais
para este design sem dependências, não bugs:

- **Apenas aliases simples são seguidos.** O rastreamento de testemunhas segue aliases
  simples do mesmo método, como `const q = qty`, mas não expressões derivadas ou
  desestruturação:  ```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
  • A vinculação entre métodos cobre apenas cadeias de helpers da mesma classe. Um helper da mesma classe sem decorador, chamado como this.verifyX(arg), pode vincular o argumento de um chamador ao estado, e desde a 0.19.0 cadeias deles (@method → helper A → helper B) são seguidas até um ponto fixo. O passo helper→helper mapeia apenas uma referência de parâmetro simples, então helperA(x.add(1)) não se propaga. Funções livres e importadas ainda não são seguidas, e o aliasing de variável local do argumento do helper permanece uma limitação documentada.

  • A detecção de Bool não asseverado tem forma de instrução. O Tier A apenas sinaliza instruções de expressão simples cuja chamada mais externa é um predicado Bool sem nada encadeado depois. Predicados aninhados dentro de Provable.if(...), ou atribuídos e usados posteriormente, não são sinalizados. Usos complexos de fluxo de controle de um local Bool ainda podem passar despercebidos se o nome nunca for referenciado (modo de falha: omissão, não um falso positivo).

  • O gating por assinatura é em nível de método e baseado em substring. _method_is_signature_gated trata um @method inteiro como gated pelo proprietário se ele contiver um idioma de assinatura, e reconhece um verificador apenas quando o nome do receptor contém literalmente signature — então sig.verify(admin, msg) não é reconhecido como gating, enquanto uma verificação de assinatura não relacionada em outro lugar de um método grande pode suprimir em excesso. É tudo ou nada por método.

  • A autenticação do remetente é baseada em nome e apenas no mesmo método. O1JS_UNCONSTRAINED_SENDER suprime quando this.sender.getAndRequireSignature() ou AccountUpdate.createSigned(<that sender>) aparece no mesmo corpo do @method. Um requisito de assinatura que vive apenas em um helper (this.requireSenderSig() → getAndRequireSignature dentro) não é seguido — o modo de falha é um falso positivo em código correto que envolve o idioma, não um bug real perdido.

  • Helpers cross-crate do Noir são reconhecidos apenas por convenção de nome (sem resolução de Nargo.toml / import). Prefira omissão a falso positivo.

Essas são a razão pela qual os achados são um ponto de partida para revisão humana, não provas. Uma reescrita com reconhecimento de fluxo de dados está deliberadamente fora do escopo do analisador léxico.

Onde esta ferramenta para

o1js-scan é deliberadamente uma passagem léxica superficial de arquivo único — sem parser, sem fluxo de dados, sem solver. É isso que o torna livre de dependências e instantâneo em CI, e também é um teto rígido. As limitações acima não são um backlog; são consequências do design.

Então vale ser explícito sobre o que esta ferramenta pode e não pode lhe dizer:

  • Uma execução limpa não é uma auditoria. Significa que nenhuma forma que este scanner reconhece correspondeu — não que o circuito esteja sólido. Classes de bugs que precisam de fluxo de dados, sensibilidade a caminho ou resolução de restrições estão fora do alcance de uma ferramenta desta forma, em qualquer linguagem.
  • Um achado é uma pista, não um veredito. Toda regra aqui é uma heurística com uma classe documentada de falsos positivos.

Essa troca é a correta para um linter que você executa em cada commit. Se você está trabalhando em algo onde a diferença importa — um protocolo que detém valor real, um circuito que você não pode errar — trate isto como a primeira passagem e reserve orçamento para uma revisão de verdade.

Para análise mais profunda, o scanner completo separado é mantido no repositório audit-engine-cli. o1js-scan é o scanner aberto intencionalmente leve; o conhecimento de detecção proprietário e os detalhes de implementação do scanner completo não são reproduzidos aqui. Para acesso ou uma revisão de circuito mais completa, entre em contato: [email protected].

Privacidade e código privado

A CLI instalada analisa arquivos localmente. Ela não tem telemetria, cliente de rede, conta ou etapa de upload, e seu runtime Python não tem dependências de terceiros. Executar o1js-scan path/to/private-repo não envia o código-fonte ou os achados a lugar nenhum.

Como logs de compilador, a saída do scanner pode conter caminhos, identificadores e fragmentos de código-fonte. O SARIF também identifica localizações exatas do repositório, e a GitHub Action o envia para o code scanning do GitHub. Use os mesmos controles de acesso de repositório e CI que você já usa para o código-fonte sendo escaneado.

Quer contribuir com um relatório útil de falso positivo ou detecção perdida sem compartilhar uma aplicação? Reproduza a sintaxe com nomes e constantes inventados, remova a lógica de negócio uma instrução por vez, e verifique se o trecho sintético ainda dispara a mesma regra antes de publicá-lo. O guia de contribuição seguro para a privacidade tem uma checklist concreta e várias formas de ajudar a comunidade o1js sem divulgar um circuito privado.

Esse limite não impede que o scanner aberto melhore. Documentação e repositórios públicos de o1js podem apoiar novas regras e fixtures de compatibilidade; exemplos sintéticos podem testar falsos positivos e restrições perdidas; e resiliência do parser, diagnósticos, SARIF, desempenho, empacotamento e calibração podem todos melhorar sem publicar uma técnica de auditoria privada ou código de cliente. O scanner aberto deve fazer afirmações independentemente explicáveis; a pesquisa privada pode permanecer no motor de auditoria separado.

Revisão pós-quântica

O risco quântico está relacionado à segurança de circuitos, mas não é uma regra de restrição ausente. o1js-scan não determina se uma assinatura, hash, commitment, o sistema de prova Kimchi, ou a própria Mina atende a um alvo de segurança pós-quântica. Essas respostas dependem da primitiva e dos parâmetros concretos, das suposições de plataforma, do tempo de vida exigido pela implantação e do seu plano de migração — não meramente de um identificador TypeScript que um scanner léxico consegue ver.

Inspirado pelo Qubit or Not Qubit da O(1) Labs, o guia de revisão pós-quântica transforma esse limite em um inventário específico de o1js e uma checklist de agilidade criptográfica. Use-o junto com este scanner em vez de interpretar uma varredura limpa como uma avaliação pós-quântica.

Compatibilidade

Funciona em o1js 1.x, 2.x e 3.x, incluindo o hard fork Mesa que o o1js 3.0.0 tem como alvo. o1js-scan analisa código-fonte TypeScript como texto e não tem dependência de runtime do o1js — nada é fixado por versão. Ele se baseia na moderna API de pré-condição require* (getAndRequireEquals, requireEquals, requireSignature, getAndRequireSignature), nos decoradores @method / @method() / @method.returns(...), campos @state anotados, this.send({...}), transferências de baixo nível AccountUpdate.balance.subInPlace(...), e Permissions.*. As formas estabelecidas permanecem compatíveis através das fronteiras 1.x → 2.x → 3.x, enquanto o scanner também aceita as variantes de decorador e transferência de baixo nível recém-documentadas. O idioma de autenticação de proprietário do 2.x this.sender.getAndRequireSignature() é reconhecido como gating de assinatura. (Pré-condições legadas assertEquals ainda são aceitas, então código mais antigo também não quebra.)

As mudanças incompatíveis do Mesa são todas em nível de runtime e protocolo — a remoção de Transaction.setFeePerSnarkCost() e das constantes TransactionCost.*, a nova forma de VerificationKey.toJSON(), chaves de verificação regeneradas, MAX_ZKAPP_STATE_FIELDS elevado de 8 para 32, e o formato de transação mina-signer v4. Nenhuma delas renomeia uma API que este scanner corresponde, então nenhuma regra mudou para o Mesa, e isso é verificado em vez de afirmado. scripts/o1js_release_matrix.sh escaneia duas releases fixadas do o1js que atravessam a fronteira de protocolo — 2.15.0 (9620ef08, a última release 2.x) e 3.0.0 (cc18a919, Mesa) — e compara cada achado contra tests/fixtures/o1js_release_matrix.json:

ReleaseAchadosHIGHMEDIUMLOWArquivos
o1js 2.15.036826218
o1js 3.0.0 (Mesa)39829219

33 achados são idênticos através da fronteira, nenhum foi perdido, e todos os três novos estão em src/examples/zkapps/big-state-zkapp.ts — o exemplo de campo de estado 32 que existe apenas porque o Mesa elevou MAX_ZKAPP_STATE_FIELDS. Esse delta é fixado por um teste, então não pode derivar silenciosamente. A matriz roda em cada build de CI; o job semanal o1js-upstream-canary adicionalmente rastreia o o1js em HEAD, à frente de qualquer release.

Grafias equivalentes de restrição são normalizadas para análise: instância assertEquals(...), estático Provable.assertEqual(Type, ...), e cadeias de igualdade equals(...).assertTrue() todas vinculam os mesmos operandos. A extração de método é balanceada por chaves após mascaramento de comentários e strings que preserva comprimento, e aceita decoradores multilinha, tipos de parâmetro aninhados em forma de callback, modificadores de acesso TypeScript, e aliases de identidade multilinha (incluindo formas parentetizadas e as Type).

A análise de Noir tem como alvo a sintaxe Noir usada por projetos Aztec / nargo (.nr); ela não invoca nargo nem compila circuitos.

Como funciona

É um analisador léxico, não um parser completo de TypeScript ou Noir — fontes o1js e Noir são delimitadas por chaves e tratáveis por regex, e a saída destina-se a ser triada por um humano. Isso o mantém livre de dependências e instantâneo para rodar em CI. Os achados são um ponto de partida para revisão, não provas.

Roadmap / contribuição

Contribuições são bem-vindas — novas famílias de regras, mais guardas de FP, e arquétipos de calibração do mundo real são todos valiosos. Veja CONTRIBUTING.md.

Para um caminho proposto da listagem de Community Packages até uma verificação de advisory no repositório o1js, veja a proposta de integração upstream do o1js pronta para envio.

Execute os testes e o linter com:```bash pip install -e ".[dev]" pytest # unit tests + Noir/o1js corpus ruff check . # lint npm run format:check # prettier, npm wrapper only

root@kitploit:~
## Licença

Apache-2.0. Consulte [`LICENSE`](https://github.com/auditinfra-io/o1js-scan/blob/main/LICENSE).
Baixar ferramenta
O1JS_STALE_MERKLE_ROOThighUm método recalcula uma raiz de Merkle a partir de uma testemunha fornecida pelo provador (computeRootAndKey / calculateRoot) mas não vincula nenhuma das raízes recalculadas à raiz on-chain atual. Sem um this.root.requireEquals(...) / assertEquals contra a raiz em vigor, um provador pode passar uma testemunha para uma árvore fabricada ou obsoleta — forjando a pertença ou repetindo estado antigo. A vinculação pode residir num helper não decorado da mesma classe (this.verifyX(witness)); a propagação de helpers cobre isso.
O1JS_UNVERIFIED_PROOFhighUm parâmetro de @method tipado como Proof<...> / SelfProof<...> / DynamicProof<...> nunca é .verify()'d antes de os seus campos públicos serem usados. Passar uma Proof não a verifica — sem uma verificação explícita o provador pode fornecer um objeto de prova arbitrário, e qualquer uso do seu publicOutput fica sem restrições. Também dispara quando .verifyIf(flag) é condicionado por um argumento de @method sem restrições e os campos públicos da prova são lidos, porque o provador pode tornar a condição falsa.
O1JS_UNASSERTED_BOOLhigh / mediumUm predicado o1js (equals / lessThanOrEqual / …) devolve um Bool e não adiciona nenhuma restrição a menos que o resultado seja asseverado ou usado. HIGH quando a chamada é uma instrução simples descartada; MEDIUM quando atribuída a uma variável local que nunca é referenciada novamente.
O1JS_UNCONSTRAINED_SENDERhigh / mediumthis.sender.getUnconstrained() devolve o remetente da tx sem o provar. HIGH quando esse valor (ou uma variável local derivada dele) flui para um assert / .set de estado / send (verificação vazia); MEDIUM caso contrário. Prefira this.sender.getAndRequireSignature(), ou o idioma expandido AccountUpdate.createSigned(sender). Mantém-se silencioso quando (1) o mesmo @method também chama this.sender.getAndRequireSignature() em qualquer lugar (o requisito de assinatura é ao nível do método), ou (2) o valor testemunhado do remetente é o argumento de AccountUpdate.createSigned(...) / um AccountUpdate.create(...).requireSignature() sobre essa mesma chave (é exigida identidade do argumento — um createSigned sobre uma chave diferente não suprime).
MissingRangeCheckhighUm Field em bruto (não o UInt64/UInt32 com verificação de intervalo) é usado como montante de transferência. Um Field é um elemento mod p e não tem limite de intervalo.
O1JS_WEAK_PERMISSIONShigh / mediumeditState / send definidos como proofOrSignature() ou none(), permitindo que a chave da conta zkApp contorne o circuito ao assinar. Também sinaliza setVerificationKey / setPermissions deixados em signature / proofOrSignature / none (as rodinhas de treino de atualização documentadas da Mina); HIGH quando combinado com um editState/send fraco no mesmo permissions.set.
O1JS_LOGIC_OUTSIDE_PROOFhighLógica de segurança (assert / approve / send / .set de estado) dentro de Provable.asProver(...) ou de um callback Provable.witness*. Esses callbacks correm fora do circuito — um provador malicioso pode eliminá-los e ainda produzir uma prova que verifica.
O1JS_APPROVE_WITHOUT_BINDINGmediumUm @method chama approve / approveAccountUpdate / approveBase sem ler balanceChange / publicKey e sem assertCanMint / assertCanBurn / uma verificação de conservação forEachUpdate — o arquétipo Mina FlawedTokenContract.
O1JS_VACUOUS_ASSERThigh / mediumUm assert que é satisfeito por construção: x.assertEquals(x), x.equals(x).assertTrue(), ou Bool(true).assertTrue(). HIGH para auto-comparações (quase sempre um erro de escrita); MEDIUM para asserts de Bool constante.
O1JS_CONDITIONAL_ASSERTmediumUm assert dentro de if <flag> { ... } onde <flag> é um Bool de @method controlado pelo provador (ou uma variável local derivada de .toBoolean()). Um condicional JS não restringe o circuito da forma como Provable.if o faz. As comparações inline permanecem não reportadas por precisão.
O1JS_GUARDED_INVERSEmediumUm .div() / .inv() / .sqrt() dentro de um ramo de Provable.if, guardado por uma condição sobre o próprio valor em que falha. Ambos os ramos são avaliados in-circuit e estas chamadas asseveram incondicionalmente que o inverso ou a raiz existe, pelo que a guarda não salta a asserção — o circuito é insatisfazível exatamente para a entrada que a guarda foi escrita para tratar, e o método nunca pode ser provado para ela. Reportado pela Veridise como V-O1J-VUL-060. Calcule primeiro um divisor seguro (Provable.if(isZero, Field(1), d)) e selecione o resultado depois. Mantém-se silencioso quando a guarda nada diz sobre o divisor, pelo que um Provable.if não relacionado em torno de uma divisão segura não é sinalizado.
O1JS_PRECONDITION_OVERWRITTENmediumDuas ou mais chamadas requireEquals / requireBetween / requireNothing sobre a mesma propriedade num método, com argumentos diferentes. As pré-condições são definidas no AccountUpdate em vez de acumuladas, pelo que cada chamada substitui a anterior e apenas a última é imposta — ao contrário das asserções in-circuit, que se compõem. a.requireEquals(b) e depois a.requireEquals(c) implica a === c, não a === b. Reportado pela Veridise como V-O1J-VUL-012. Mantém-se silencioso quando os argumentos são idênticos (idempotente, nada se perde), em getAndRequireEquals() (um método diferente, pelo que leituras repetidas de estado são aceitáveis), e quando as chamadas se situam em ramos JS mutuamente exclusivos, que são resolvidos no momento de construção do circuito. Essa última isenção pode ocultar uma sobreposição real que atravessa um if/else não relacionado.
O1JS_STATE_READ_AFTER_WRITEmediumUm campo @state é lido (get() / getAndRequireEquals()) depois de um set(...) sobre o mesmo campo concluir, no mesmo método. set() regista a alteração no AccountUpdate mas não a escreve de volta para get(), pelo que a leitura ainda observa o valor anterior à escrita e qualquer aritmética construída sobre ele está silenciosamente desviada por essa escrita. Reportado pela Veridise como V-O1J-VUL-030. Mantenha o novo valor numa variável local em vez de ler o estado de volta. Mantém-se silencioso quando a leitura está aninhada nos próprios argumentos da escrita (o idioma read-modify-write this.x.set(this.x.getAndRequireEquals().add(1)), que está correto), e quando a escrita e a leitura se situam em ramos JS mutuamente exclusivos. Limitado a um único método — o caso de cache entre métodos que a Veridise também descreve requer conhecimento do grafo de chamadas que esta regra não tem.
index.assert_max_bit_size::<8>(); let i = index as u32;
assert_eq
NOIR_UNASSERTED_BOOLhigh / mediumUma comparação cujo resultado bool é descartado. Análogo de O1JS_UNASSERTED_BOOL do o1js.
NOIR_CONDITIONAL_ASSERTmediumUm assert dentro de if <flag> { ... } onde <flag> é um bool simples controlado pelo provador ou uma variável local derivada de valores controlados pelo provador. Uma restrição dentro de um condicional só se aplica quando a condição é verdadeira, pelo que um ramo escolhido pelo provador pode saltar a verificação. As comparações inline (if x != 0) são deixadas em paz por precisão; atribuir a guarda a uma variável local (let gate = x != 0; if gate) é reportado a menos que gate seja ele próprio asseverado.
NOIR_CONDITIONAL_CONSTRAINmediumUma chamada constrain_* / confirm_* / verify_* apenas sob um if controlado pelo provador, enquanto uma dica unsafe ainda atinge a saída.
NOIR_UNUSED_CHECK_RESULThigh / mediumUm resultado de check_* / confirm_* / verify_* / constrain_* é descartado (chamada simples) ou atribuído e nunca asseverado — a verificação não vincula o circuito.
NOIR_VACUOUS_CONSTRAINThigh / mediumUma restrição que é satisfeita por construção: uma auto-comparação (assert(x == x), assert_eq(x, x), x >= x) ou uma condição constante (assert(true)). Não adiciona nenhuma restrição, mas a linha lê-se como uma verificação — o que a torna mais perigosa do que uma restrição em falta, porque a revisão para aí. HIGH para uma auto-comparação (quase sempre um erro de escrita para uma verificação real: assert(computed == expected) mal escrito como assert(expected == expected)); MEDIUM para uma constante, que é mais frequentemente um placeholder. x != x não é sinalizado — isso é insatisfazível, um bug de liveness em vez de um buraco de solidez silencioso.
NOIR_UNSAFE_MISSING_SAFETYlowUm bloco unsafe { ... } sem um comentário // Safety: adjacente. Informativo; não falha a CI no --fail-on high padrão.