● Zion Boggan
repos/Cyclops V2/orchestrator/orchestrator.py
zionboggan.com ↗
120 lines · python
History for this file →
1
"""
2
Cyclops v2 - orchestrator loop.
3
 
4
Ties the pillars into one queue-driven loop:
5
    feed (fresh targets) -> brain (dataflow hypotheses) -> oracle (execution
6
    proof) -> memory gate (finding only with proof) -> vault projection.
7
 
8
This is intentionally THIN. The intelligence lives in the brain queries, the
9
oracle harnesses, and the severity gate - not in a 164-tool buffet. The loop's
10
job is sequencing, caching, and refusing to skip the proof step.
11
 
12
Run modes:
13
    orchestrator.py feed
14
    orchestrator.py drain-targets
15
    orchestrator.py drain-oracle
16
    orchestrator.py status
17
"""
18
import os, sys, subprocess, json
19
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "memory"))
20
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "brain"))
21
from memory import Memory
22
 
23
CYCLOPS_HOME = os.environ.get("CYCLOPS_HOME", "/opt/cyclops-v2")
24
PY = os.environ.get("CYCLOPS_PY", "/opt/cyclops-venv/bin/python")
25
SRC_CACHE = os.path.join(CYCLOPS_HOME, "targets", "src")
26
 
27
def _checkout(repo, tag):
28
    """Shallow-clone a target at a tag into the src cache. Returns local path."""
29
    os.makedirs(SRC_CACHE, exist_ok=True)
30
    name = repo.replace("github.com/", "").replace("/", "__")
31
    dest = os.path.join(SRC_CACHE, name)
32
    url = "https://" + repo + ".git"
33
    if not os.path.isdir(dest):
34
        subprocess.run(["git", "clone", "--quiet", "--depth", "1",
35
                        "--branch", tag, url, dest],
36
                       capture_output=True, text=True)
37
    if not os.path.isdir(dest):
38
        subprocess.run(["git", "clone", "--quiet", url, dest], capture_output=True)
39
        subprocess.run(["git", "-C", dest, "checkout", "-q", tag], capture_output=True)
40
    return dest
41
 
42
def cmd_feed():
43
    subprocess.run([PY, os.path.join(CYCLOPS_HOME, "orchestrator", "feed.py")])
44
 
45
def cmd_drain_targets(limit=20):
46
    m = Memory()
47
    n = 0
48
    while n < limit:
49
        job = m.dequeue("targets", timeout=2)
50
        if not job:
51
            break
52
        repo, arch, tag = job["repo"], job["archetype"], job.get("tag", "HEAD")
53
        print(f"[orch] target {repo}@{tag} [{arch}]")
54
        src = _checkout(repo, tag)
55
        if not os.path.isdir(src):
56
            print(f"[orch] checkout failed: {repo}@{tag}")
57
            continue
58
        lang = _guess_lang(src)
59
        subprocess.run([PY, os.path.join(CYCLOPS_HOME, "brain", "brain.py"),
60
                        repo, arch, src, lang, tag])
61
        n += 1
62
    print(f"[orch] drained {n} targets")
63
 
64
def cmd_drain_oracle(limit=20):
65
    """Pull brain's oracle jobs. For memory-corruption hypotheses on C targets we
66
    can auto-fuzz; other sink classes are queued for a harness author (logged)."""
67
    m = Memory()
68
    n = 0
69
    while n < limit:
70
        job = m.dequeue("oracle", timeout=2)
71
        if not job:
72
            break
73
        hyps = job.get("hypotheses", [])
74
        mem = [h for h in hyps if h["sink_class"] == "memory-corruption"]
75
        print(f"[orch] oracle job {job['repo']}: {len(hyps)} hyps "
76
              f"({len(mem)} auto-fuzzable memory-corruption)")
77
        for h in mem:
78
            m.enqueue("harness_author", {"target": job["target"], "repo": job["repo"],
79
                                         "src": job["src"], "hyp": h})
80
        for h in hyps:
81
            if h["sink_class"] != "memory-corruption":
82
                m.enqueue("detonation", {"target": job["target"], "repo": job["repo"],
83
                                         "src": job["src"], "hyp": h})
84
        n += 1
85
    print(f"[orch] routed {n} oracle jobs (harness_author + detonation queues)")
86
 
87
def cmd_status():
88
    m = Memory()
89
    print("=== GRAPH ===")
90
    for k, v in m.stats().items():
91
        print(f"  {k}: {v}")
92
    print("=== QUEUES ===")
93
    for q in ("targets", "oracle", "harness_author", "detonation"):
94
        print(f"  {q}: {m.r.llen('cyclops:q:'+q)}")
95
    print("=== FINDINGS (gated) ===")
96
    res = m._exec("""MATCH (f:Finding) RETURN f.severity, f.impact_family, f.title
97
                     ORDER BY f.severity""")
98
    while res.has_next():
99
        r = res.get_next()
100
        print(f"  [{r[0]}] {r[1]} :: {r[2]}")
101
 
102
def _guess_lang(src):
103
    import collections
104
    ext = collections.Counter()
105
    for root, _, files in os.walk(src):
106
        if "/.git" in root:
107
            continue
108
        for f in files:
109
            e = f.rsplit(".", 1)[-1].lower() if "." in f else ""
110
            if e in ("c", "h", "cc", "cpp", "cxx"): ext["c"] += 1
111
            elif e in ("py",): ext["python"] += 1
112
            elif e in ("js", "ts", "jsx", "tsx"): ext["javascript"] += 1
113
            elif e in ("java",): ext["java"] += 1
114
            elif e in ("go",): ext["go"] += 1
115
    return ext.most_common(1)[0][0] if ext else ""
116
 
117
if __name__ == "__main__":
118
    cmd = sys.argv[1] if len(sys.argv) > 1 else "status"
119
    {"feed": cmd_feed, "drain-targets": cmd_drain_targets,
120
     "drain-oracle": cmd_drain_oracle, "status": cmd_status}.get(cmd, cmd_status)()