o1js-scan 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- o1js_scan/__init__.py +33 -0
- o1js_scan/cli.py +60 -0
- o1js_scan/lexer.py +743 -0
- o1js_scan/vuln.py +55 -0
- o1js_scan-0.1.0.dist-info/METADATA +355 -0
- o1js_scan-0.1.0.dist-info/RECORD +10 -0
- o1js_scan-0.1.0.dist-info/WHEEL +5 -0
- o1js_scan-0.1.0.dist-info/entry_points.txt +2 -0
- o1js_scan-0.1.0.dist-info/licenses/LICENSE +202 -0
- o1js_scan-0.1.0.dist-info/top_level.txt +1 -0
o1js_scan/__init__.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""o1js-scan — a static soundness analyzer for o1js / Mina zkApps.
|
|
2
|
+
|
|
3
|
+
Public API:
|
|
4
|
+
analyze_file(filepath, source) -> list[Vulnerability]
|
|
5
|
+
analyze_project(root) -> list[(filepath, Vulnerability)]
|
|
6
|
+
is_o1js_source(content, path) -> bool
|
|
7
|
+
O1jsLexer -> the analyzer class
|
|
8
|
+
Severity, Vulnerability -> finding types
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from .lexer import (
|
|
14
|
+
O1JS_ORIGIN_TIER,
|
|
15
|
+
O1jsLexer,
|
|
16
|
+
analyze_file,
|
|
17
|
+
analyze_project,
|
|
18
|
+
is_o1js_source,
|
|
19
|
+
)
|
|
20
|
+
from .vuln import Severity, Vulnerability
|
|
21
|
+
|
|
22
|
+
__version__ = "0.1.0"
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"O1jsLexer",
|
|
26
|
+
"analyze_file",
|
|
27
|
+
"analyze_project",
|
|
28
|
+
"is_o1js_source",
|
|
29
|
+
"Severity",
|
|
30
|
+
"Vulnerability",
|
|
31
|
+
"O1JS_ORIGIN_TIER",
|
|
32
|
+
"__version__",
|
|
33
|
+
]
|
o1js_scan/cli.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Command-line entry point for o1js-scan.
|
|
2
|
+
|
|
3
|
+
o1js-scan <path> [--json]
|
|
4
|
+
|
|
5
|
+
Exits 1 when any high/critical finding is present (CI-friendly), else 0.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import json
|
|
12
|
+
import sys
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import List, Optional
|
|
15
|
+
|
|
16
|
+
from .lexer import analyze_project
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main(argv: Optional[List[str]] = None) -> int:
|
|
20
|
+
ap = argparse.ArgumentParser(
|
|
21
|
+
prog="o1js-scan",
|
|
22
|
+
description="o1js (Mina/Kimchi) zkApp application-layer soundness scanner.",
|
|
23
|
+
)
|
|
24
|
+
ap.add_argument("path", help="file or directory to scan")
|
|
25
|
+
ap.add_argument("--json", action="store_true", help="emit JSONL findings")
|
|
26
|
+
args = ap.parse_args(argv)
|
|
27
|
+
|
|
28
|
+
# Fail loudly on a missing path. Otherwise a typo'd scan target silently
|
|
29
|
+
# produces zero findings and exit 0 — a green CI run that scanned nothing.
|
|
30
|
+
if not Path(args.path).exists():
|
|
31
|
+
print(f"o1js-scan: path not found: {args.path}", file=sys.stderr)
|
|
32
|
+
return 2
|
|
33
|
+
|
|
34
|
+
findings = analyze_project(args.path)
|
|
35
|
+
|
|
36
|
+
if args.json:
|
|
37
|
+
for fp, v in findings:
|
|
38
|
+
print(json.dumps({
|
|
39
|
+
"file": fp,
|
|
40
|
+
"rule_id": v.rule_id,
|
|
41
|
+
"severity": v.severity.value.lower(),
|
|
42
|
+
"function": v.function,
|
|
43
|
+
"line": (v.location or [0])[0],
|
|
44
|
+
"title": v.title,
|
|
45
|
+
"evidence": v.evidence,
|
|
46
|
+
}))
|
|
47
|
+
else:
|
|
48
|
+
if not findings:
|
|
49
|
+
print("o1js-scan: no findings (or no o1js files found)", file=sys.stderr)
|
|
50
|
+
for fp, v in findings:
|
|
51
|
+
line = (v.location or [0])[0]
|
|
52
|
+
print(f"{v.severity.value.upper():<8} {v.rule_id:<34} "
|
|
53
|
+
f"{Path(fp).name}:{line} fn={v.function} {v.title}")
|
|
54
|
+
|
|
55
|
+
hi = any(v.severity.value.lower() in ("critical", "high") for _f, v in findings)
|
|
56
|
+
return 1 if hi else 0
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if __name__ == "__main__":
|
|
60
|
+
sys.exit(main())
|