Skip to content

feat(hub): add Strix Hub — multi-tenant task orchestration & dual-channel model routing console - #1170

Closed
Genesiu wants to merge 9 commits into
usestrix:mainfrom
Genesiu:feat/strix-hub-web-orchestration
Closed

feat(hub): add Strix Hub — multi-tenant task orchestration & dual-channel model routing console#1170
Genesiu wants to merge 9 commits into
usestrix:mainfrom
Genesiu:feat/strix-hub-web-orchestration

Conversation

@Genesiu

@Genesiu Genesiu commented Aug 26, 2026

Copy link
Copy Markdown

Summary

This PR introduces Strix Hub (strix hub), a zero-intrusion, multi-tenant task orchestration and dual-channel model routing console for Strix.

Motivation

While Strix has an excellent headless CLI and read-only viewer (strix view), security teams often require:

  1. Interactive Task Lifecycle Management: Launching scans from a web console, live pause (SIGSTOP with 0 CPU & 0 token cost), resume (SIGCONT), and termination without needing direct SSH/terminal access.
  2. Dual-Channel Model Routing (Cloud Brain + Local Muscles):
    • Root Agent (Brain): High-reasoning cloud models (Gemini 3.1 Pro, Claude 3.7 Sonnet, GPT-4o) for strategic vulnerability discovery and high-dimensional orchestration.
    • Sub-agents (Muscles): Private/local models (e.g. Qwen 3.8 / DeepSeek / Ollama / vLLM / SGLang) for high-concurrency port scanning, fuzzing, and payload execution with zero token costs and zero rate limits.
  3. Auto Tool-Call Recovery: Built-in adapter to automatically extract and convert plain text / XML-formatted tool calls into standard OpenAI JSON tool_calls for maximum compatibility with open-weights LLMs.
  4. Multi-Tenancy & User Isolation: Built-in SQLite persistence and RBAC so team members manage their own pentest tasks while administrators oversee the entire fleet.

Key Architectural Highlights

  • Zero External Dependencies: Built entirely on Python standard libraries (http.server, sqlite3, subprocess, threading) and a single-file modern Dark Mode React SPA.
  • Zero-Intrusion Facade: Strix core engine remains 100% untouched.
  • CLI Integration: Can be launched directly via strix hub [--port 8888] or as a standalone module python -m strix_hub.main.

Usage

# Launch Strix Hub Web Management on port 8888
strix hub --port 8888

Open http://localhost:8888 in browser.

  • Default Admin: admin / admin123

@greptile-apps

greptile-apps Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Strix Hub adds a network-accessible multi-tenant console, SQLite-backed task and user state, process lifecycle controls, and per-task dual-channel model routing. The implementation currently contains several blocking launch, command-execution, credential-handling, task-attribution, and lifecycle defects.

  • Adds strix hub CLI dispatch and a standalone HTTP/SPA service.
  • Adds user sessions, RBAC, persistent tasks, and model credentials.
  • Adds process-group task controls and per-task model-router gateways.
  • Adds model preset management and XML tool-call recovery.

Confidence Score: 0/5

This PR is not safe to merge until the command injection, known administrator credential, broken CLI and presets paths, credential logging, and task-state correctness failures are fixed.

Authenticated task input reaches a shell interpreter, fresh network deployments expose a universal administrator login, provider keys enter logs, the advertised CLI cannot parse its invocation, and concurrent or stopped tasks can persist incorrect results and status.

Files Needing Attention: strix/interface/main.py, strix_hub/db.py, strix_hub/server.py, strix_hub/task_manager.py, strix_hub/model_router.py

Security Review

The task runner permits authenticated shell-command injection, the default network deployment provisions a publicly known administrator credential, and router hot reloads write raw provider API keys to logs.

Important Files Changed

Filename Overview
strix/interface/main.py Adds Hub dispatch, but leaves the consumed subcommand in sys.argv, preventing the documented CLI invocation from starting.
strix_hub/db.py Adds persistence and RBAC data operations but automatically provisions a known administrator password.
strix_hub/server.py Adds the REST API and SPA, but the presets endpoint raises on undefined configuration names.
strix_hub/task_manager.py Adds process orchestration but introduces shell-command injection, cross-task artifact attribution, and incorrect stopped-task reconciliation.
strix_hub/model_router.py Adds dual-channel proxying and tool-call recovery but logs raw model-provider credentials during hot reload.
strix_hub/main.py Defines standalone Hub argument parsing and defaults to a network-wide binding, amplifying the impact of seeded credentials.
Prompt To Fix All With AI
### Issue 1
strix/interface/main.py:434-438
**Hub subcommand remains unconsumed**

When `strix hub --port 8888` is invoked, `run_hub()` parses the unchanged `sys.argv`, so argparse treats `hub` as an unknown argument and exits instead of starting the server.

### Issue 2
strix_hub/task_manager.py:103-106
**Task fields reach shell**

When an authenticated user supplies shell syntax in `target`, `scan_mode`, or `instruction`, these values are interpolated into a string executed with `shell=True`, causing arbitrary commands to run with the Hub process's privileges.

**How this was verified:** The request fields flow from `POST /api/tasks` through persistence into the shell-interpreted command without shell-safe argument separation.

### Issue 3
strix_hub/db.py:115-126
**Known administrator credential seeded**

On a fresh installation, the service binds to all interfaces and provisions the publicly documented `admin` / `admin123` account, allowing a network peer to authenticate as administrator and control every tenant's tasks, logs, users, and model configuration.

**How this was verified:** Database initialization creates the literal credential while the default server configuration exposes the login service on `0.0.0.0`.

### Issue 4
strix_hub/server.py:184-186
**Preset variables are undefined**

When the SPA requests `/api/models/presets`, the handler reads three undefined `LOCAL_QWEN38_*` names, raising `NameError` and preventing presets and local model defaults from loading.

```suggestion
                        "model": LOCAL_LLM_MODEL,
                        "url": LOCAL_LLM_URL,
                        "key": LOCAL_LLM_KEY,
```

### Issue 5
strix_hub/model_router.py:60
**Router logs raw credentials**

When model routing configuration is hot-reloaded, the info-level log serializes the complete configuration dictionary, exposing raw root and subagent API keys to application logs and configured log collectors.

**How this was verified:** The logged dictionary directly contains the `root_api_key` and `subagent_api_key` values.

```suggestion
            logger.info("ModelRouter config hot-updated")
```

### Issue 6
strix_hub/task_manager.py:303-329
**Artifacts cross task boundaries**

If multiple tasks run concurrently, each monitor selects the globally newest Strix run directory without associating it with its own task, causing one tenant's task to display another scan's run directory and vulnerability count.

### Issue 7
strix_hub/task_manager.py:283-286
**Stopped tasks become failed**

When an operator terminates a task, `stop_task` records `stopped`, but the monitor subsequently maps the signal exit code to `failed` and overwrites that state, causing intentional terminations to appear as failures.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "feat(hub): add Strix Hub multi-tenant ta..." | Re-trigger Greptile

Comment thread strix/interface/main.py Outdated
Comment on lines +434 to +438
# `strix hub …` launches the multi-tenant task orchestration and dual-channel router console.
if len(sys.argv) > 1 and sys.argv[1] == "hub":
from strix_hub.main import main as run_hub

run_hub()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Hub subcommand remains unconsumed

When strix hub --port 8888 is invoked, run_hub() parses the unchanged sys.argv, so argparse treats hub as an unknown argument and exits instead of starting the server.

Prompt To Fix With AI
This is a comment left during a code review.
Path: strix/interface/main.py
Line: 434-438

Comment:
**Hub subcommand remains unconsumed**

When `strix hub --port 8888` is invoked, `run_hub()` parses the unchanged `sys.argv`, so argparse treats `hub` as an unknown argument and exits instead of starting the server.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread strix_hub/task_manager.py Outdated
Comment on lines +103 to +106
cmd = f"{strix_bin} -n --target {target} --scan-mode {scan_mode}"
if instruction:
clean_inst = instruction.replace('"', '\\"')
cmd += f' --instruction "{clean_inst}"'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Task fields reach shell

When an authenticated user supplies shell syntax in target, scan_mode, or instruction, these values are interpolated into a string executed with shell=True, causing arbitrary commands to run with the Hub process's privileges.

How this was verified: The request fields flow from POST /api/tasks through persistence into the shell-interpreted command without shell-safe argument separation.

Prompt To Fix With AI
This is a comment left during a code review.
Path: strix_hub/task_manager.py
Line: 103-106

Comment:
**Task fields reach shell**

When an authenticated user supplies shell syntax in `target`, `scan_mode`, or `instruction`, these values are interpolated into a string executed with `shell=True`, causing arbitrary commands to run with the Hub process's privileges.

**How this was verified:** The request fields flow from `POST /api/tasks` through persistence into the shell-interpreted command without shell-safe argument separation.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread strix_hub/db.py
Comment on lines +115 to +126
def ensure_admin_user() -> None:
with get_connection() as conn:
cursor = conn.cursor()
cursor.execute("SELECT id FROM users WHERE role = 'admin' LIMIT 1")
if cursor.fetchone() is None:
admin_id = f"user_{secrets.token_hex(6)}"
p_hash, salt = hash_password("admin123")
cursor.execute(
"INSERT INTO users (id, username, password_hash, salt, role, created_at) VALUES (?, ?, ?, ?, ?, ?)",
(admin_id, "admin", p_hash, salt, "admin", int(time.time())),
)
conn.commit()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Known administrator credential seeded

On a fresh installation, the service binds to all interfaces and provisions the publicly documented admin / admin123 account, allowing a network peer to authenticate as administrator and control every tenant's tasks, logs, users, and model configuration.

How this was verified: Database initialization creates the literal credential while the default server configuration exposes the login service on 0.0.0.0.

Prompt To Fix With AI
This is a comment left during a code review.
Path: strix_hub/db.py
Line: 115-126

Comment:
**Known administrator credential seeded**

On a fresh installation, the service binds to all interfaces and provisions the publicly documented `admin` / `admin123` account, allowing a network peer to authenticate as administrator and control every tenant's tasks, logs, users, and model configuration.

**How this was verified:** Database initialization creates the literal credential while the default server configuration exposes the login service on `0.0.0.0`.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread strix_hub/server.py Outdated
Comment on lines +184 to +186
"model": LOCAL_QWEN38_MODEL,
"url": LOCAL_QWEN38_URL,
"key": LOCAL_QWEN38_KEY,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Preset variables are undefined

When the SPA requests /api/models/presets, the handler reads three undefined LOCAL_QWEN38_* names, raising NameError and preventing presets and local model defaults from loading.

Suggested change
"model": LOCAL_QWEN38_MODEL,
"url": LOCAL_QWEN38_URL,
"key": LOCAL_QWEN38_KEY,
"model": LOCAL_LLM_MODEL,
"url": LOCAL_LLM_URL,
"key": LOCAL_LLM_KEY,
Prompt To Fix With AI
This is a comment left during a code review.
Path: strix_hub/server.py
Line: 184-186

Comment:
**Preset variables are undefined**

When the SPA requests `/api/models/presets`, the handler reads three undefined `LOCAL_QWEN38_*` names, raising `NameError` and preventing presets and local model defaults from loading.

```suggestion
                        "model": LOCAL_LLM_MODEL,
                        "url": LOCAL_LLM_URL,
                        "key": LOCAL_LLM_KEY,
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread strix_hub/model_router.py Outdated
self.config[k] = v.rstrip("/")
else:
self.config[k] = v
logger.info("ModelRouter config hot-updated: %s", self.config)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Router logs raw credentials

When model routing configuration is hot-reloaded, the info-level log serializes the complete configuration dictionary, exposing raw root and subagent API keys to application logs and configured log collectors.

How this was verified: The logged dictionary directly contains the root_api_key and subagent_api_key values.

Suggested change
logger.info("ModelRouter config hot-updated: %s", self.config)
logger.info("ModelRouter config hot-updated")
Prompt To Fix With AI
This is a comment left during a code review.
Path: strix_hub/model_router.py
Line: 60

Comment:
**Router logs raw credentials**

When model routing configuration is hot-reloaded, the info-level log serializes the complete configuration dictionary, exposing raw root and subagent API keys to application logs and configured log collectors.

**How this was verified:** The logged dictionary directly contains the `root_api_key` and `subagent_api_key` values.

```suggestion
            logger.info("ModelRouter config hot-updated")
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread strix_hub/task_manager.py Outdated
Comment on lines +303 to +329
def _inspect_strix_runs_dir(task_id: str) -> tuple[str | None, int]:
"""Inspect newest run artifacts to find linked run_dir and vulnerability counts."""
if not STRIX_RUNS_DIR.is_dir():
return None, 0

runs = sorted(
[d for d in STRIX_RUNS_DIR.iterdir() if d.is_dir() and not d.name.startswith(".")],
key=lambda p: p.stat().st_mtime,
reverse=True,
)

if not runs:
return None, 0

latest = runs[0]
vuln_count = 0
vulns_file = latest / "vulnerabilities.json"
if vulns_file.is_file():
try:
with open(vulns_file, "r", encoding="utf-8") as f:
vulns = json.load(f)
if isinstance(vulns, list):
vuln_count = len(vulns)
except Exception:
pass

return latest.name, vuln_count

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Artifacts cross task boundaries

If multiple tasks run concurrently, each monitor selects the globally newest Strix run directory without associating it with its own task, causing one tenant's task to display another scan's run directory and vulnerability count.

Prompt To Fix With AI
This is a comment left during a code review.
Path: strix_hub/task_manager.py
Line: 303-329

Comment:
**Artifacts cross task boundaries**

If multiple tasks run concurrently, each monitor selects the globally newest Strix run directory without associating it with its own task, causing one tenant's task to display another scan's run directory and vulnerability count.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread strix_hub/task_manager.py
Comment on lines +283 to +286
run_dir_name, vulns_cnt = _inspect_strix_runs_dir(task_id)

final_status = "completed" if exit_code in [0, 2] else "failed"
db.update_task_status(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Stopped tasks become failed

When an operator terminates a task, stop_task records stopped, but the monitor subsequently maps the signal exit code to failed and overwrites that state, causing intentional terminations to appear as failures.

Prompt To Fix With AI
This is a comment left during a code review.
Path: strix_hub/task_manager.py
Line: 283-286

Comment:
**Stopped tasks become failed**

When an operator terminates a task, `stop_task` records `stopped`, but the monitor subsequently maps the signal exit code to `failed` and overwrites that state, causing intentional terminations to appear as failures.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants