mirror of
https://github.com/myronblair/jarvis
synced 2026-06-30 17:50:23 -05:00
Fix 8 code-review findings: security + reliability
1. agent.py: shell allow-check reads cfg, not server payload (RCE fix) 2. webhook.php: move WEBHOOK_SECRET to gitignored config.php; rotate secret 3. agent.py: replace recursive main() with while loop (RecursionError fix) 4. jarvis-deploy.sh: push force-revert to GitHub on syntax fail (loop fix) 5. agent.py: self_update() verifies SHA-256 before exec (integrity fix) 6. agent.php: remove JARVIS_IP from browser-action bypass (auth fix) 7. jarvis-watchdog.sh: escape SQL vars in alert() to prevent injection 8. jarvis-deploy.sh: atomic mv instead of cat+truncate (TOCTOU fix) Also: distribute jarvis-agent.py.sha256 alongside agent for integrity checks
This commit is contained in:
@@ -331,9 +331,9 @@ def execute_command(cmd: dict) -> dict:
|
||||
return {"success": True, "updated": updated}
|
||||
|
||||
elif cmd_type == "shell":
|
||||
# Only allow if explicitly enabled in config
|
||||
if not cmd_data.get("allowed", False):
|
||||
return {"success": False, "error": "Shell commands not enabled"}
|
||||
# Guard reads LOCAL config, not the server-supplied payload
|
||||
if not cfg.get("allow_shell_commands", False):
|
||||
return {"success": False, "error": "Shell commands not enabled in agent config"}
|
||||
cmd_str = cmd_data.get("command", "")
|
||||
r = subprocess.run(cmd_str, shell=True, capture_output=True, text=True, timeout=30)
|
||||
return {"success": True, "stdout": r.stdout[:2000], "stderr": r.stderr[:500]}
|
||||
@@ -359,15 +359,13 @@ def main():
|
||||
poll_interval = int(cfg.get("poll_interval", 30))
|
||||
heartbeat_every = int(cfg.get("heartbeat_every", 10))
|
||||
|
||||
# Register if no API key yet
|
||||
# Register if no API key yet — loop (not recurse) to avoid stack overflow
|
||||
api_key = state.get("api_key", "")
|
||||
if not api_key:
|
||||
while not api_key:
|
||||
api_key = register(cfg, state)
|
||||
if not api_key:
|
||||
print("[ERROR] Could not register with JARVIS. Retrying in 60s...", flush=True)
|
||||
time.sleep(60)
|
||||
main()
|
||||
return
|
||||
|
||||
headers = {"X-Agent-Key": api_key}
|
||||
last_metrics = 0
|
||||
@@ -424,7 +422,9 @@ def main():
|
||||
# ── Self-update ────────────────────────────────────────────────────────────────
|
||||
|
||||
def self_update(cfg: dict) -> bool:
|
||||
"""Check JARVIS server for a newer version of this script. If different, replace and restart."""
|
||||
"""Check JARVIS server for a newer version of this script.
|
||||
Verifies SHA-256 hash from <update_url>.sha256 before replacing."""
|
||||
import hashlib
|
||||
jarvis_url = cfg.get("jarvis_url", "").rstrip("/")
|
||||
default_update_url = f"{jarvis_url}/agent/jarvis-agent.py" if jarvis_url else ""
|
||||
update_url = cfg.get("update_url", default_update_url)
|
||||
@@ -432,14 +432,37 @@ def self_update(cfg: dict) -> bool:
|
||||
return False
|
||||
script_path = os.path.abspath(__file__)
|
||||
try:
|
||||
# Download expected hash first
|
||||
hash_url = update_url + ".sha256"
|
||||
req_hash = urllib.request.Request(hash_url)
|
||||
req_hash.add_header("User-Agent", "JARVIS-Agent/1.0")
|
||||
if _host_header:
|
||||
req_hash.add_header("Host", _host_header)
|
||||
try:
|
||||
with urllib.request.urlopen(req_hash, timeout=10) as resp:
|
||||
expected_hash = resp.read().decode().strip().split()[0]
|
||||
except Exception:
|
||||
expected_hash = None
|
||||
|
||||
# Download new script
|
||||
req = urllib.request.Request(update_url)
|
||||
req.add_header("User-Agent", "JARVIS-Agent/1.0")
|
||||
if _host_header:
|
||||
req.add_header("Host", _host_header)
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
new_content = resp.read()
|
||||
|
||||
# Verify hash if available — abort if mismatch
|
||||
if expected_hash:
|
||||
actual_hash = hashlib.sha256(new_content).hexdigest()
|
||||
if actual_hash != expected_hash:
|
||||
print(f"[JARVIS] Update hash mismatch (expected {expected_hash[:16]}… got {actual_hash[:16]}…) — aborting", flush=True)
|
||||
return False
|
||||
|
||||
with open(script_path, "rb") as f:
|
||||
current = f.read()
|
||||
if new_content != current:
|
||||
print(f"[JARVIS] Update available — replacing {script_path} and restarting...", flush=True)
|
||||
print(f"[JARVIS] Update verified — replacing {script_path} and restarting...", flush=True)
|
||||
with open(script_path, "wb") as f:
|
||||
f.write(new_content)
|
||||
os.execv(sys.executable, [sys.executable] + sys.argv)
|
||||
|
||||
Reference in New Issue
Block a user