Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
o1js-scan — Analizzatore statico senza dipendenze per bug di solidità dei circuiti zk in o1js/Mina zkApps e circuiti Noir | Kitploit
Strumenti/GitHubGitHub/auditinfra-io/o1js-scan
Strumenti DifensiviAnalisi StaticaScanner di VulnerabilitàAnalisi Statica del Codice (SAST)Analisi delle VulnerabilitàAnalisi del CodiceCrittografiaDevSecOps
GitHubauditinfra-io/o1js-scan

o1js-scan

Analizzatore statico senza dipendenze per bug di solidità dei circuiti zk in o1js/Mina zkApps e circuiti Noir

Vedi Repository
2104 giorni faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Sito web
Condividi

o1js-scan

CI Python License PyPI npm

Pacchetto della community: o1js-scan è elencato nella directory ufficiale o1js Community Packages.

Ultima versione: 0.20.0 — l'analizzatore ora legge i contratti che extends TokenContract. Fino a questa release il gate dei contratti corrispondeva solo a SmartContract, quindi ogni token fungibile, collezione NFT e pool AMM dell'ecosistema veniva scansionato come "nessun risultato". Se hai scansionato un contratto token prima della 0.20.0, scansiona di nuovo. Vedi .

CHANGELOG

Un analizzatore statico veloce e senza dipendenze per bug di soundness dei circuiti zk in:

  • o1js / Mina zkApps (TypeScript .ts / .js) — circuiti Kimchi dai corpi dei @method
  • Noir (.nr) — il DSL ZK in stile Rust di Aztec (inclusi pattern con struttura aztec-nr)

I bug critici per la sicurezza di solito non sono nel sistema di proving — sono nei vincoli dell'applicazione stessa: witness che il prover controlla ma che il circuito non vincola mai. o1js-scan è lo scanner per segnali sotto-vincolati per i cugini di Circom negli ecosistemi 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:~
### Esempio

Dato un vault il cui importo di `withdraw` è un witness controllato dal prover che
non è mai vincolato allo stato 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 è necessario qui solo perché il file demo si trova sotto examples/, che il classificatore di percorsi declassa per impostazione predefinita affinché il codice di esempio di un repository non possa far fallire la sua build. Lo stesso contratto nel tuo src/ segnala HIGH senza alcun flag.

Il risultato HIGH è il bug prosciugabile. Il contratto corretto (examples/safe_vault.ts) lo elimina ed esce con 0, mantenendo solo il LOW informativo sul destinatario scelto dal 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:~
Consulta [`examples/`](https://github.com/auditinfra-io/o1js-scan/blob/main/examples) per le coppie vulnerabili/corrette di o1js e Noir.

## Contenuti

- [Installazione](#install)
- [Utilizzo](#usage) · [Sopprimere un risultato](#suppressing-a-reviewed-finding)
- [GitHub Action](#github-action)
- [Cosa rileva — o1js](#what-it-detects-o1js) · [Noir](#what-it-detects-noir)
- [Limitazioni note](#known-limitations) · [Dove si ferma questo strumento](#where-this-tool-stops)
- [Privacy e codice privato](#privacy-and-private-code)
- [Revisione post-quantum](#post-quantum-review)
- [Compatibilità](#compatibility) · [Come funziona](#how-it-works)
- [Contribuire](#roadmap--contributing)

## Installazione```bash
pip install o1js-scan

Per un'installazione CLI globale isolata, usa pipx:```bash pipx install o1js-scan

root@kitploit:~
Per i repository di app Noir, Aztec o o1js basati su Node/npm, installa il wrapper npm:```bash
npm install -D o1js-scan
npx noir-scan . --lang noir --fail-on high

Il pacchetto npm è un sottile wrapper attorno allo stesso analizzatore Python e richiede Python 3.8+ nel PATH (python3 o python). Imposta O1JS_SCAN_PYTHON per scegliere un interprete specifico.

Oppure dai sorgenti:```bash git clone https://github.com/auditinfra-io/o1js-scan cd o1js-scan pip install -e .

root@kitploit:~
Nessuna dipendenza Python di terze parti. Python 3.8+. Lo script console `noir-scan` è
installato insieme a `o1js-scan` (stesso entry point), anche tramite il wrapper
npm.

## Utilizzo```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

Il codice di uscita è 1 quando è presente un finding pari o superiore al livello --fail-on (predefinito high) e 0 altrimenti — così puoi inserirlo direttamente nella CI. Con il valore predefinito, un finding di livello low/medium (inclusa la regola informativa sul destinatario di seguito) non fa fallire la build; usa --fail-on none per limitarti a segnalare, oppure --strict (una scorciatoia per --fail-on medium) per applicare un gate più rigoroso trattando comunque i finding di bassa severità come advisory. Le due opzioni sono mutuamente esclusive, così la configurazione della CI non può essere ambigua. Un percorso di scansione mancante esce con 2 e un errore su stderr, così un refuso non può passare silenziosamente in CI come una scansione pulita. Ogni esecuzione stampa un riepilogo di una riga (conteggi per severità e verdetto del gate) su stderr.

Il codice di test è escluso per impostazione predefinita — su entrambi i backend. I test costruiscono deliberatamente valori non validi e transazioni errate per dimostrare che gli assert le rifiutano, quindi un finding lì è lo scopo del test, non un bug del circuito. Un file è considerato codice di test quando:

  • il suo nome corrisponde a *.test.ts / *.spec.ts (e alle varianti .js/.jsx/.tsx/.mjs/.cjs), oppure a *_test.nr / test_*.nr;
  • si trova sotto una directory test/, tests/, __tests__/, spec/ o __mocks__/;
  • (solo Noir, basato sul contenuto) la funzione porta un attributo #[test] / #[test(...)], oppure si trova all'interno di un blocco mod test { … } / mod tests { … } — con ambito di blocco, quindi un modulo di test in fondo a un file di produzione non silenzia il resto del file.

Passa --include-tests per segnalarli.

Il codice di esempio viene declassato, non eliminato. Un finding in una directory examples/ o example/, o in un file chiamato *.eg.ts (anche .nr e le altre estensioni JS/TS), viene abbassato a LOW con una nota — ancora segnalato, ma non più in grado di far fallire una build. Il codice di esempio è deliberatamente semplificato, e segnalare gli esempi di un framework stesso come vulnerabilità è rumore; ma viene copiato in produzione molto più spesso del codice di test, ed è per questo che viene declassato anziché nascosto. Passa --include-examples per mantenere la severità originale.

Ogni volta che si applica una delle due politiche, l'esecuzione stampa una riga su stderr che lo indica — ad esempio 6 file(s) skipped as test code, 1 finding(s) downgraded as examples — così una scansione silenziosa non è mai silenziosamente tale. I conteggi compaiono anche nel SARIF sotto invocation.properties. Nota il compromesso: il rilevamento è basato solo sul percorso (nessun parsing di describe(/it(), quindi un circuito di produzione archiviato sotto tests/ verrà saltato — la riga su stderr è il modo in cui te ne accorgi.

Directory saltate durante l'attraversamento di un albero: node_modules, target (nargo), .git, dist, build, __pycache__, .venv, venv.

Sopprimere un finding revisionato

Silenzia un finding che hai già analizzato senza allentare il gate, con un commento inline sulla riga segnalata — o sulla riga sopra di essa:```ts this.send({ to, amount }); // o1js-scan-disable-line O1JS_UNCONSTRAINED_WITNESS

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

root@kitploit:~
| `-s` | `--server` | `SERVER` | `http://localhost:8080` | URL del server MCP |
| `-t` | `--token` | `TOKEN` | `None` | Token di autenticazione |
| `-k` | `--insecure` | flag | `False` | Disabilita la verifica del certificato TLS |
| `-v` | `--verbose` | flag | `False` | Abilita il logging dettagliato |
| `-o` | `--output` | `FILE` | `None` | Salva l'output su file |
| `-f` | `--format` | `FORMAT` | `text` | Formato di output (`text`, `json`) |
| `-c` | `--config` | `FILE` | `None` | File di configurazione |
| `-h` | `--help` | flag | - | Mostra il messaggio di aiuto |

### Esempi

```bash
# Elenca tutti gli strumenti disponibili
mcp-client --server http://localhost:8080 --list-tools

# Chiama uno strumento specifico
mcp-client --server http://localhost:8080 --call-tool scan_target --args '{"target": "example.com"}'

# Usa un file di configurazione
mcp-client --config ~/.mcp/config.json --list-tools

# Output in formato JSON
mcp-client --server http://localhost:8080 --list-tools --format json

Configurazione

Il client MCP supporta un file di configurazione JSON:

root@kitploit:~
{
  "server": "http://localhost:8080",
  "token": "your-auth-token",
  "insecure": false,
  "verbose": false,
  "timeout": 30,
  "retries": 3
}

Posizionalo in ~/.mcp/config.json o specifica un percorso personalizzato con --config.

Sviluppo

Configurazione dell'ambiente

root@kitploit:~
# Clona il repository
git clone https://github.com/example/mcp-client.git
cd mcp-client

# Crea un ambiente virtuale
python3 -m venv venv
source venv/bin/activate  # Su Windows: venv\Scripts\activate

# Installa le dipendenze di sviluppo
pip install -r requirements-dev.txt

# Installa in modalità modificabile
pip install -e .

Esecuzione dei test

root@kitploit:~
# Esegui tutti i test
pytest

# Esegui con copertura
pytest --cov=mcp_client --cov-report=html

# Esegui test specifici
pytest tests/test_client.py -v

Linting e formattazione

root@kitploit:~
# Esegui il linting del codice
ruff check .

# Formatta il codice
ruff format .

# Esegui il type checking
mypy mcp_client/

Sicurezza

  • Autenticazione: Supporta l'autenticazione basata su token tramite l'header Authorization
  • TLS: Verifica i certificati TLS per impostazione predefinita; usa --insecure solo per lo sviluppo
  • Validazione dell'input: Tutti gli input vengono validati prima dell'invio
  • Timeout: Timeout configurabile per prevenire richieste bloccate
  • Rate limiting: Rispetta gli header di rate limiting del server

Risoluzione dei problemi

Impossibile connettersi al server

root@kitploit:~
# Verifica che il server sia in esecuzione
curl http://localhost:8080/health

# Controlla la connettività di rete
ping localhost

# Verifica la configurazione del firewall
sudo ufw status

Errori di autenticazione

root@kitploit:~
# Verifica che il token sia impostato
echo $MCP_TOKEN

# Testa l'autenticazione manualmente
curl -H "Authorization: Bearer $MCP_TOKEN" http://localhost:8080/tools

Timeout

Aumenta il timeout nel file di configurazione o usa il flag --timeout:

root@kitploit:~
mcp-client --server http://localhost:8080 --timeout 60 --list-tools

Contribuire

  1. Fai il fork del repository
  2. Crea un branch per la funzionalità (git checkout -b feature/amazing-feature)
  3. Esegui il commit delle modifiche (git commit -m 'Add amazing feature')
  4. Esegui il push sul branch (git push origin feature/amazing-feature)
  5. Apri una Pull Request

Licenza

Questo progetto è rilasciato sotto la Licenza MIT - vedi il file LICENSE per i dettagli.

Ringraziamenti

  • Model Context Protocol per la specifica
  • Tutti i contributori che hanno reso possibile questo progetto

Supporto

  • 📖 Documentazione
  • 🐛 Segnalazione di bug
  • 💬 Discussioni

Nota: Questo strumento è destinato esclusivamente a test di sicurezza autorizzati e ricerca. Gli autori non sono responsabili per qualsiasi uso improprio o danno causato da questo software.```nr let inv = unsafe { hint(x) }; // o1js-scan-disable-line NOIR_UNCONSTRAINED_WITNESS

root@kitploit:~
Elenca uno o più id di regole per sopprimere solo quelle; una direttiva nuda (senza id)
sopprime ogni regola sulla riga di destinazione.

Come libreria:```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

Aggiungi lo scanner alla CI in poche righe. I risultati appaiono come annotazioni sul diff della PR e come avvisi nella scheda Security → Code scanning del repository.```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:~
### Ricetta CI solo per Noir

Consigliata per i progetti Noir che desiderano avvisi di scansione del codice e un gate per le vulnerabilità ad alta gravità:```yaml
- uses: auditinfra-io/[email protected]
  with:
    path: .
    lang: noir
    fail-on: high

O senza l'Action:```bash pip install o1js-scan noir-scan . --lang noir --fail-on high --sarif noir.sarif

root@kitploit:~
### pre-commit (opzionale)```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"]

Input: path (predefinito .), lang (auto|o1js|noir, predefinito auto), version (versione PyPI da installare, predefinito l'ultima), upload-sarif (predefinito true), fail-on (critical|high|medium|low|none, predefinito none), fail-on-findings (deprecato, predefinito false), include-tests (predefinito false), include-examples (predefinito false). Output: sarif-file. Il caricamento SARIF richiede security-events: write e il code scanning abilitato.

Il report e il gate sono costruiti da un unico array di argomenti, quindi include-tests e include-examples si applicano a entrambi — il SARIF che leggi e il codice di uscita su cui applichi il gate descrivono sempre lo stesso insieme di sorgenti. La passata di reporting viene eseguita con --fail-on none in modo che i risultati non blocchino mai il caricamento SARIF, ma un errore operativo (un percorso che non esiste, un errore d'uso della CLI) fa comunque fallire lo step invece di essere segnalato come scansione pulita.

fail-on-findings: true è mantenuto per compatibilità e viene mappato a fail-on: high quando fail-on è lasciato a none; emette un avviso di deprecazione. Preferisci fail-on, che può applicare il gate a qualsiasi severità.

Cosa rileva (o1js)

Regole supportate in sintesi

BackendRegoleHigh-capableMedium-capableLow-capable
o1js1811122
Noir11491
Totale2915213

I conteggi sono ID di regole distinti supportati da ciascun backend. Una regola che assegna la severità in base al contesto (ad esempio, high per un trasferimento di valore e medium per una scrittura di stato) compare in più di una colonna di severità, quindi le colonne di severità intenzionalmente non sommano al totale delle regole. Attualmente non esistono regole di severità critical o info. Le descrizioni complete e le protezioni contro i falsi positivi seguono di seguito.

RegolaSeveritàCosa significa
O1JS_MISSING_STATE_PRECONDITIONhighthis.x.get() letto senza un corrispondente requireEquals(...) / getAndRequireEquals(). Un get() nudo non aggiunge nessuna precondizione sull'account, quindi la proof non vincola x al suo valore on-chain — un prover può sostituire qualsiasi valore.
O1JS_UNCONSTRAINED_WITNESShigh / mediumUn argomento di @method (un witness privato controllato dal prover) confluisce in un importo di invio (this.send(...) o un AccountUpdate.create*(...).send(...) dello stesso metodo) o in un .set(...) di stato e non viene mai asserito. Analogo diretto di un segnale Circom sotto-vincolato. High quando raggiunge un trasferimento di valore.
O1JS_UNCONSTRAINED_PROVABLE_WITNESShigh / medium / lowUna variabile locale Provable.witness(...) confluisce in un effetto di invio/stato con nessuna asserzione nel circuito. Il callback del witness viene eseguito fuori dal circuito (è solo un suggerimento per il prover), quindi il risultato è un valore fresco controllato dal prover — l'altra fonte di witness oltre agli argomenti di @method. Deve essere ri-derivato e asserito (x.assertEquals(<recomputed>)) o vincolato allo stato. High su un importo di invio (this.send(...) o AccountUpdate.create* dello stesso metodo).
O1JS_UNCONSTRAINED_RECIPIENTlowUn argomento di @method è usato solo come destinatario to: di un invio. Di solito è intenzionale (un utente indica la propria destinazione di prelievo) ed è informativo — conta solo se la destinazione deve essere una tesoreria fissa o un indirizzo registrato nello stato. Non attiva il gate del codice di uscita della CI.
O1JS_WITNESS_NOT_BOUND_TO_STATEmediumUn witness è vincolato solo trivialmente (ad es. > 0, o confrontato con una costante) prima di un effetto — mai legato allo stato on-chain. Conferma che l'orchestrazione off-chain lo renda sicuro, altrimenti il saldo è drenabile fino al suo valore corrente.

Protezioni contro i falsi positivi (o1js)

L'analizzatore è progettato per restare silenzioso su codice corretto:

  • I metodi protetti da firma vengono saltati. Un @method che chiama this.requireSignature() (o getAndRequireSignature, AccountUpdate.createSigned, Signature.verify) è protetto da owner/admin — i suoi argomenti sono scelti dal detentore della chiave, non da un prover arbitrario — quindi i suoi witness non vengono segnalati. Questo è l'equivalente o1js di onlyOwner.
  • I witness vincolati allo stato vengono saltati. Un argomento asserito uguale a (o limitato da un confronto d'ordinamento rispetto a) un valore derivato da getAndRequireEquals() è corretto e non verrà segnalato. Questo copre sia la forma diretta — amount.assertLessThanOrEqual(bal) — sia la forma concatenata amount.lessThanOrEqual(bal).assertTrue(). Anche il binding che risiede in un helper non decorato della stessa classe (this.verifyX(arg)) è riconosciuto, anche attraverso una catena di tali helper.
  • Le proof verificate vengono saltate. Un argomento tipizzato Proof / SelfProof / DynamicProof / *Proof su cui viene chiamato .verify() è vincolato dal circuito verificato — i risultati sui witness su di esso (e sui suoi publicOutput / publicInput) sono soppressi. Un .verifyIf(flag) è accreditato solo quando la condizione non è un argomento di metodo non vincolato, o è essa stessa asserita. Lo stesso vale per il wrapper canonico OffchainState this.offchainState.settle(proof) (il framework verifica dentro settle). Un .settle(proof) scritto a mano non si presume che verifichi. Il caso inverso (argomento tipizzato proof mai verificato e non regolato da OffchainState) è segnalato come O1JS_UNVERIFIED_PROOF.
  • I Bool asseriti / usati vengono saltati. Un predicato concatenato con .assertTrue() / .assertFalse(), annidato in Provable.if(...), o assegnato a una variabile locale successivamente referenziata, non è segnalato come O1JS_UNASSERTED_BOOL.
  • I mittenti autenticati vengono saltati. this.sender.getUnconstrained() non scatta quando lo stesso @method chiama anche this.sender.getAndRequireSignature(), o quando quel valore witness è passato a AccountUpdate.createSigned(...) / autenticato tramite .requireSignature() su un AccountUpdate costruito da esso (è richiesta l'identità dell'argomento).
  • I commenti e i letterali stringa vengono rimossi prima dell'analisi, quindi un assert dentro una stringa non può creare un risultato falso.

Cosa rileva (Noir)

La stessa idea di correttezza — witness sotto-vincolati — si applica ai circuiti Noir (.nr). Punta lo scanner su file .nr (o usa --lang noir) e li analizza con il set di regole Noir. Stesso approccio lessicale, senza dipendenze. Calibrato sugli idiomi oracle / unsafe di aztec-nr — vedi docs/noir_calibration.md.

RegolaSeveritàCosa significa
NOIR_UNCONSTRAINED_WITNESShighUn valore vincolato da un blocco unsafe { ... } — il risultato di una unconstrained fn (hint oracle / Brillig) — che non viene mai ri-vincolato da un assert / assert_eq (o un helper di conferma / controllo merkle). L'hint viene eseguito fuori dal circuito. Analogo di O1JS_UNCONSTRAINED_PROVABLE_WITNESS.
NOIR_UNCONSTRAINED_INPUTmediumUn input privato (witness) di fn main che non confluisce in nessun assert / assert_eq e non fa parte dell'output pubblico. Analogo di O1JS_UNCONSTRAINED_WITNESS.
NOIR_UNCONSTRAINED_PUBLIC_INPUTmediumUn input pubblico di fn main che non raggiunge alcun vincolo e nessun output — il circuito non lo legge mai. Il duale della regola sul witness privato: il verificatore fornisce il valore e crede che l'asserzione lo riguardi, mentre il circuito lo ignora (ad es. un merkle_root: pub Field che non viene mai controllato, quindi l'appartenenza non è mai stata realmente provata). MEDIUM perché un input pubblico deliberatamente inutilizzato è anche un idioma legittimo per vincolare una proof a un contesto (nonce / chain id / destinatario), che è lessicalmente indistinguibile — quindi non applica il gate della CI al valore predefinito --fail-on high.
NOIR_UNCHECKED_CASTmediumUn valore controllato dal prover convertito a un tipo unsigned stretto (as u8/u16/u32) con nessuna asserzione di intervallo. Analogo di MissingRangeCheck di o1js.
NOIR_UNCONSTRAINED_ARRAY_INDEXmediumUn valore controllato dal prover usato come indice di array (arr[i]) con nessun controllo di alcun tipo su di esso. Il controllo implicito dei limiti di Noir stabilisce solo che l'indice è nell'intervallo — non che sia l'indice corretto — quindi il prover resta libero di selezionare qualsiasi elemento e produrre comunque una proof che verifica. Questo è il bug della libertà di selezione dietro le posizioni dei percorsi di Merkle, la selezione delle note e l'appartenenza alle allow-list. Soppresso quando l'indice è limitato a un intervallo, fissato da un'uguaglianza, limitato prima di un cast (), o quando il valore riletto è esso stesso fissato da un .

Protezioni contro i falsi positivi (Noir)

  • Gli helper di assert / let-hop / conferma nello stesso file vincolano gli hint unsafe.
  • I nomi dei call-site constrain_* / confirm_* / verify_* / check_(non_)membership* / public_data_storage_read accreditano gli argomenti (con rilevamento dei risultati inutilizzati per i controlli scartati).
  • Unconstrained intenzionale documentato (richiede // Safety: adiacente): random(), avm::…, e la formulazione differita kernel/rollup/discovery.
  • let su tupla + flag asseriti vincolano i witness merkle passati ai controlli di appartenenza.

Esempio:```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:~
Come con l'esempio o1js sopra, `--include-examples` è necessario solo perché
questi file demo si trovano sotto `examples/`.

## Limitazioni note

L'analizzatore è un **frontend lessicale senza dipendenze più un livello semantico leggero** che effettua il tracciamento degli alias e la propagazione interprocedurale attraverso helper della stessa classe. Non è un frontend del compilatore TypeScript, un type checker o un motore di dataflow sull'intero programma, e non c'è alcun livello SMT o di prova formale in questo scanner.
Tieni presenti questi punti ciechi durante la triage — sono noti e intenzionali
per questo design senza dipendenze, non bug:

- **Vengono seguiti solo gli alias semplici.** Il tracciamento dei witness segue
  alias semplici dello stesso metodo come `const q = qty`, ma non espressioni
  derivate o destrutturazione:  ```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
  • Il binding cross-method copre solo catene di helper della stessa classe. Un helper non decorato della stessa classe chiamato come this.verifyX(arg) può fare state-binding dell'argomento di un chiamante, e dalla 0.19.0 le catene di questi (@method → helper A → helper B) vengono seguite fino a un punto fisso. Il passaggio helper→helper mappa solo un riferimento a parametro nudo, quindi helperA(x.add(1)) non si propaga. Le funzioni libere e importate non vengono ancora seguite, e l'aliasing tramite variabile locale dell'argomento dell'helper resta una limitazione documentata.

  • Il rilevamento di Bool non asseriti è basato sulla forma dell'istruzione. Il Tier A segnala solo istruzioni di espressione nude la cui chiamata più esterna è un predicato Bool senza nulla concatenato dopo. I predicati annidati dentro Provable.if(...), o assegnati e usati successivamente, non vengono segnalati. Usi complessi di controllo di flusso di un Bool locale possono ancora sfuggire se il nome non viene mai referenziato (modalità di fallimento: mancata rilevazione, non un falso positivo).

  • Il signature-gating è a livello di metodo e basato su sottostringhe. _method_is_signature_gated tratta un intero @method come owner-gated se contiene un idioma di firma, e riconosce un verificatore solo quando il nome del receiver contiene letteralmente signature — quindi sig.verify(admin, msg) non viene riconosciuto come gating, mentre un controllo di firma non correlato altrove in un metodo grande può sopprimere eccessivamente. È tutto-o-niente per metodo.

  • L'autenticazione del sender è basata sul nome e solo nello stesso metodo. O1JS_UNCONSTRAINED_SENDER sopprime quando this.sender.getAndRequireSignature() o AccountUpdate.createSigned(<quel sender>) appare nello stesso corpo del @method. Un requisito di firma che risiede solo in un helper (this.requireSenderSig() → getAndRequireSignature all'interno) non viene seguito — la modalità di fallimento è un falso positivo su codice corretto che incapsula l'idioma, non un bug reale mancato.

  • Gli helper cross-crate di Noir sono riconosciuti solo per convenzione di nome (nessuna risoluzione di Nargo.toml / import). Preferire la mancata rilevazione al falso positivo.

Questi sono il motivo per cui i risultati sono un punto di partenza per la revisione umana, non prove. Una riscrittura consapevole del dataflow è deliberatamente fuori ambito per l'analizzatore lessicale.

Dove si ferma questo strumento

o1js-scan è deliberatamente un passaggio lessicale superficiale su singolo file — nessun parser, nessun dataflow, nessun solver. È ciò che lo rende privo di dipendenze e istantaneo in CI, ed è anche un tetto invalicabile. Le limitazioni sopra non sono un backlog; sono conseguenze del design.

Quindi vale la pena essere espliciti su cosa questo strumento può e non può dirti:

  • Un'esecuzione pulita non è un audit. Significa che nessuna forma riconosciuta da questo scanner ha trovato corrispondenza — non che il circuito sia corretto. Classi di bug che richiedono dataflow, path sensitivity o constraint solving sono fuori portata per uno strumento di questa forma, in qualsiasi linguaggio.
  • Un risultato è un indizio, non un verdetto. Ogni regola qui è un'euristica con una classe documentata di falsi positivi.

Questo compromesso è quello giusto per un linter che esegui a ogni commit. Se stai lavorando a qualcosa dove la differenza conta — un protocollo che detiene valore reale, un circuito che non puoi permetterti di sbagliare — trattalo come il primo passaggio e metti in budget una revisione seria.

Per un'analisi più approfondita, lo scanner completo separato è mantenuto nel repository audit-engine-cli. o1js-scan è lo scanner intenzionalmente leggero e aperto; la conoscenza proprietaria di rilevamento e i dettagli implementativi dello scanner completo non sono riprodotti qui. Per accesso o una revisione del circuito più completa, contatta: [email protected].

Privacy e codice privato

La CLI installata analizza i file localmente. Non ha telemetria, client di rete, account o passaggio di upload, e il suo runtime Python non ha dipendenze di terze parti. Eseguire o1js-scan path/to/private-repo non invia il sorgente o i risultati da nessuna parte.

Come i log del compilatore, l'output dello scanner può contenere percorsi, identificatori e frammenti di sorgente. SARIF identifica anche posizioni esatte del repository, e la GitHub Action lo carica su GitHub code scanning. Usa gli stessi controlli di accesso al repository e alla CI che già usi per il sorgente scansionato.

Vuoi contribuire con un utile report di falso positivo o di mancata rilevazione senza condividere un'applicazione? Riproduci la sintassi con nomi e costanti inventati, rimuovi la logica di business una istruzione alla volta, e verifica che lo snippet sintetico attivi ancora la stessa regola prima di pubblicarlo. La guida ai contributi privacy-safe ha una checklist concreta e diversi modi per aiutare la comunità o1js senza divulgare un circuito privato.

Questo confine non impedisce allo scanner aperto di migliorare. La documentazione pubblica di o1js e i repository possono supportare nuove regole e fixture di compatibilità; esempi sintetici possono testare falsi positivi e vincoli mancati; e la resilienza del parser, la diagnostica, SARIF, le prestazioni, il packaging e la calibrazione possono tutti migliorare senza pubblicare una tecnica di audit privata o codice cliente. Lo scanner aperto dovrebbe fare affermazioni spiegabili in modo indipendente; la ricerca privata può rimanere nel motore di audit separato.

Revisione post-quantum

Il rischio quantistico è correlato alla sicurezza del circuito, ma non è una regola di vincolo mancante. o1js-scan non determina se una firma, un hash, un commitment, il sistema di proof Kimchi o Mina stessa soddisfi un obiettivo di sicurezza post-quantum. Quelle risposte dipendono dalla primitiva e dai parametri concreti, dalle assunzioni della piattaforma, dalla durata richiesta del deployment e dal suo piano di migrazione — non semplicemente da un identificatore TypeScript che uno scanner lessicale può vedere.

Ispirata a Qubit or Not Qubit di O(1) Labs, la guida alla revisione post-quantum trasforma quel confine in un inventario specifico per o1js e una checklist di crypto-agility. Usala insieme a questo scanner invece di interpretare una scansione pulita come una valutazione post-quantum.

Compatibilità

Funziona su o1js 1.x, 2.x e 3.x, incluso l'hard fork Mesa a cui punta o1js 3.0.0. o1js-scan analizza il sorgente TypeScript come testo e non ha alcuna dipendenza a runtime da o1js — nulla è vincolato a una versione. Si basa sulla moderna API di precondizioni require* (getAndRequireEquals, requireEquals, requireSignature, getAndRequireSignature), sui decoratori @method / @method() / @method.returns(...), sui campi @state annotati, su this.send({...}), sui trasferimenti di basso livello AccountUpdate.balance.subInPlace(...) e su Permissions.*. Le forme consolidate restano compatibili attraverso i confini 1.x → 2.x → 3.x, mentre lo scanner accetta anche le varianti di decoratore e di trasferimento di basso livello appena documentate. L'idioma di owner-auth di 2.x this.sender.getAndRequireSignature() è riconosciuto come signature-gating. (Le precondizioni legacy assertEquals sono ancora accettate, quindi anche il codice più vecchio non si rompe.)

I cambiamenti di rottura di Mesa sono tutti a livello di runtime e protocollo — la rimozione di Transaction.setFeePerSnarkCost() e delle costanti TransactionCost.*, la nuova forma di VerificationKey.toJSON(), le verification key rigenerate, MAX_ZKAPP_STATE_FIELDS aumentato da 8 a 32, e il formato di transazione mina-signer v4. Nessuno di essi rinomina un'API su cui questo scanner fa match, quindi nessuna regola è cambiata per Mesa, e ciò è verificato anziché asserito. scripts/o1js_release_matrix.sh scansiona due release o1js fissate che stanno a cavallo del confine di protocollo — 2.15.0 (9620ef08, l'ultima release 2.x) e 3.0.0 (cc18a919, Mesa) — e confronta ogni risultato con tests/fixtures/o1js_release_matrix.json:

ReleaseFindingsHIGHMEDIUMLOWFiles
o1js 2.15.036826218
o1js 3.0.0 (Mesa)39829219

33 risultati sono identici attraverso il confine, nessuno è andato perso, e tutti e tre quelli nuovi sono in src/examples/zkapps/big-state-zkapp.ts — l'esempio con 32 campi di stato che esiste solo perché Mesa ha alzato MAX_ZKAPP_STATE_FIELDS. Quel delta è fissato da un test, quindi non può andare alla deriva silenziosamente. La matrice viene eseguita a ogni build CI; il job settimanale o1js-upstream-canary traccia inoltre o1js a HEAD, in anticipo su qualsiasi release.

Le grafie equivalenti dei vincoli sono normalizzate per l'analisi: assertEquals(...) di istanza, Provable.assertEqual(Type, ...) statico, e le catene di uguaglianza equals(...).assertTrue() legano tutti gli stessi operandi. L'estrazione dei metodi è brace-balanced dopo masking di commenti e stringhe a lunghezza preservata, e accetta decoratori multilinea, tipi di parametro annidati a forma di callback, modificatori di accesso TypeScript e alias di identità multilinea (incluse le forme tra parentesi e as Type).

L'analisi Noir prende di mira la sintassi Noir usata dai progetti Aztec / nargo (.nr); non invoca nargo né compila circuiti.

Come funziona

È un analizzatore lessicale, non un parser completo TypeScript o Noir — i sorgenti o1js e Noir sono delimitati da parentesi graffe e trattabili con regex, e l'output è pensato per essere valutato da un umano. Questo lo mantiene privo di dipendenze e istantaneo da eseguire in CI. I risultati sono un punto di partenza per la revisione, non prove.

Roadmap / contribuire

Contributi benvenuti — nuove famiglie di regole, più guardie FP e archetipi di calibrazione reali sono tutti preziosi. Vedi CONTRIBUTING.md.

Per un percorso proposto dalla voce Community Packages a un controllo advisory nel repository o1js, vedi la proposta di integrazione upstream o1js pronta da inviare.

Esegui i test e il 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:~
## Licenza

Apache-2.0. Vedi [`LICENSE`](https://github.com/auditinfra-io/o1js-scan/blob/main/LICENSE).
Scarica lo strumento
O1JS_STALE_MERKLE_ROOThighUn metodo ricalcola una radice di Merkle da un witness fornito dal prover (computeRootAndKey / calculateRoot) ma non vincola nessuna delle radici ricalcolate alla radice on-chain corrente. Senza un this.root.requireEquals(...) / assertEquals rispetto alla radice attiva, un prover può passare un witness per un albero fabbricato o obsoleto — falsificando l'appartenenza o riproducendo uno stato vecchio. Il binding può risiedere in un helper non decorato della stessa classe (this.verifyX(witness)); la propagazione degli helper copre questo caso.
O1JS_UNVERIFIED_PROOFhighUn parametro di @method tipizzato come Proof<...> / SelfProof<...> / DynamicProof<...> non viene mai sottoposto a .verify() prima che i suoi campi pubblici siano usati. Passare una Proof non la verifica — senza una verify esplicita il prover può fornire un oggetto proof arbitrario, e qualsiasi uso del suo publicOutput è non vincolato. Scatta anche quando .verifyIf(flag) è condizionato da un argomento di @method non vincolato e i campi pubblici della proof vengono letti, perché il prover può rendere la condizione falsa.
O1JS_UNASSERTED_BOOLhigh / mediumUn predicato o1js (equals / lessThanOrEqual / …) restituisce un Bool e non aggiunge nessun vincolo a meno che il risultato non sia asserito o usato. HIGH quando la chiamata è un'istruzione nuda scartata; MEDIUM quando è assegnata a una variabile locale mai più referenziata.
O1JS_UNCONSTRAINED_SENDERhigh / mediumthis.sender.getUnconstrained() restituisce il mittente della tx senza provarlo. HIGH quando quel valore (o una variabile locale da esso derivata) confluisce in un assert / .set di stato / send (controllo vacuo); MEDIUM altrimenti. Preferisci this.sender.getAndRequireSignature(), o l'idioma esteso AccountUpdate.createSigned(sender). Resta silenzioso quando (1) lo stesso @method chiama anche this.sender.getAndRequireSignature() ovunque (il requisito di firma è a livello di metodo), oppure (2) il valore del mittente witness è l'argomento di AccountUpdate.createSigned(...) / un AccountUpdate.create(...).requireSignature() sulla stessa chiave (è richiesta l'identità dell'argomento — un createSigned su una chiave diversa non sopprime).
MissingRangeCheckhighUn Field grezzo (non il UInt64/UInt32 con controllo di intervallo) è usato come importo di trasferimento. Un Field è un elemento mod p e non è limitato a un intervallo.
O1JS_WEAK_PERMISSIONShigh / mediumeditState / send impostati a proofOrSignature() o none(), lasciando che la chiave dell'account zkApp aggiri il circuito firmando. Segnala anche setVerificationKey / setPermissions lasciati a signature / proofOrSignature / none (le ruote di addestramento per l'upgrade documentate da Mina); HIGH quando combinati con un editState/send debole nello stesso permissions.set.
O1JS_LOGIC_OUTSIDE_PROOFhighLogica di sicurezza (assert / approve / send / .set di stato) dentro Provable.asProver(...) o un callback Provable.witness*. Quei callback vengono eseguiti fuori dal circuito — un prover malevolo può eliminarli e produrre comunque una proof che verifica.
O1JS_APPROVE_WITHOUT_BINDINGmediumUn @method chiama approve / approveAccountUpdate / approveBase senza leggere balanceChange / publicKey e senza assertCanMint / assertCanBurn / un controllo di conservazione forEachUpdate — l'archetipo Mina FlawedTokenContract.
O1JS_VACUOUS_ASSERThigh / mediumUn assert soddisfatto per costruzione: x.assertEquals(x), x.equals(x).assertTrue(), o Bool(true).assertTrue(). HIGH per auto-confronti (quasi sempre un refuso); MEDIUM per assert su Bool costanti.
O1JS_CONDITIONAL_ASSERTmediumUn assert dentro if <flag> { ... } dove <flag> è un Bool di @method controllato dal prover (o una variabile locale da .toBoolean()). Un condizionale JS non vincola il circuito come fa Provable.if. I confronti inline restano non segnalati per precisione.
O1JS_GUARDED_INVERSEmediumUn .div() / .inv() / .sqrt() dentro un ramo di Provable.if, protetto da una condizione proprio sul valore su cui fallisce. Entrambi i rami sono valutati nel circuito e queste chiamate asseriscono incondizionatamente che l'inverso o la radice esista, quindi la guardia non salta l'asserzione — il circuito è insoddisfacibile esattamente per l'input che la guardia doveva gestire, e il metodo non può mai essere provato per esso. Segnalato da Veridise come V-O1J-VUL-060. Calcola prima un divisore sicuro (Provable.if(isZero, Field(1), d)) e seleziona il risultato dopo. Resta silenzioso quando la guardia non dice nulla sul divisore, quindi un Provable.if non correlato attorno a una divisione sicura non viene segnalato.
O1JS_PRECONDITION_OVERWRITTENmediumDue o più chiamate requireEquals / requireBetween / requireNothing sulla stessa proprietà in un metodo, con argomenti diversi. Le precondizioni vengono impostate sull'AccountUpdate anziché accumulate, quindi ogni chiamata sovrascrive la precedente e solo l'ultima è applicata — a differenza delle asserzioni nel circuito, che si compongono. a.requireEquals(b) poi a.requireEquals(c) implica a === c, non a === b. Segnalato da Veridise come V-O1J-VUL-012. Resta silenzioso quando gli argomenti sono identici (idempotente, nulla perso), su getAndRequireEquals() (un metodo diverso, quindi letture ripetute dello stato vanno bene), e quando le chiamate si trovano in rami JS mutuamente esclusivi, che vengono risolti al momento della costruzione del circuito. Quest'ultima esenzione può nascondere una sovrascrittura reale che attraversa un if/else non correlato.
O1JS_STATE_READ_AFTER_WRITEmediumUn campo @state viene letto (get() / getAndRequireEquals()) dopo che un set(...) sullo stesso campo è completato, nello stesso metodo. set() registra la modifica sull'AccountUpdate ma non la propaga a get(), quindi la lettura osserva ancora il valore precedente alla scrittura e qualsiasi aritmetica costruita su di esso è silenziosamente sfasata di quella scrittura. Segnalato da Veridise come V-O1J-VUL-030. Mantieni il nuovo valore in una variabile locale invece di rileggere lo stato. Resta silenzioso quando la lettura è annidata negli argomenti stessi della scrittura (l'idioma read-modify-write this.x.set(this.x.getAndRequireEquals().add(1)), che è corretto), e quando la scrittura e la lettura si trovano in rami JS mutuamente esclusivi. Limitato a un singolo metodo — il caso di caching tra metodi che Veridise descrive richiede una conoscenza del call-graph che questa regola non possiede.
index.assert_max_bit_size::<8>(); let i = index as u32;
assert_eq
NOIR_UNASSERTED_BOOLhigh / mediumUn confronto il cui risultato bool viene scartato. Analogo di O1JS_UNASSERTED_BOOL di o1js.
NOIR_CONDITIONAL_ASSERTmediumUn assert dentro if <flag> { ... } dove <flag> è un bool nudo controllato dal prover o una variabile locale derivata da valori controllati dal prover. Un vincolo dentro un condizionale si applica solo quando la condizione è vera, quindi un ramo scelto dal prover può saltare il controllo. I confronti inline (if x != 0) vengono lasciati stare per precisione; assegnare la guardia a una variabile locale (let gate = x != 0; if gate) viene segnalato a meno che gate non sia esso stesso asserito.
NOIR_CONDITIONAL_CONSTRAINmediumUna chiamata constrain_* / confirm_* / verify_* solo sotto un if controllato dal prover, mentre un hint unsafe raggiunge comunque l'output.
NOIR_UNUSED_CHECK_RESULThigh / mediumUn risultato check_* / confirm_* / verify_* / constrain_* viene scartato (chiamata nuda) o assegnato e mai asserito — il controllo non vincola il circuito.
NOIR_VACUOUS_CONSTRAINThigh / mediumUn vincolo soddisfatto per costruzione: un auto-confronto (assert(x == x), assert_eq(x, x), x >= x) o una condizione costante (assert(true)). Non aggiunge alcuna restrizione, ma la riga si legge come un controllo — il che la rende più pericolosa di un vincolo mancante, perché la revisione si ferma lì. HIGH per un auto-confronto (quasi sempre un refuso per un controllo reale: assert(computed == expected) scritto per errore come assert(expected == expected)); MEDIUM per una costante, che è più spesso un segnaposto. x != x non viene segnalato — è insoddisfacibile, un bug di liveness piuttosto che un buco di correttezza silenzioso.
NOIR_UNSAFE_MISSING_SAFETYlowUn blocco unsafe { ... } senza un commento // Safety: adiacente. Informativo; non fa fallire la CI al valore predefinito --fail-on high.