Files
jarvisChat/tests/test_skills_framework.py
gramps 5986c4ad86 fix: close two CSRF origin-check security gaps
- Extend origin check to all /api/ requests (not just state-changing methods),
  closing the GET/HEAD/OPTIONS bypass that allowed cross-origin reads
- origin_allowed() now returns False when both Origin and Referer headers
  are absent, preventing script-initiated requests from bypassing the check
- Update AGENTS.md and README.md to document the changes
2026-06-27 15:20:02 -07:00

98 lines
3.2 KiB
Python

import asyncio
import os
from pathlib import Path
from fastapi.testclient import TestClient
import app
import db
from rag import build_system_prompt
from security import SESSIONS, PIN_ATTEMPTS, RATE_EVENTS
def make_client(tmp_path: Path) -> TestClient:
os.environ["JARVISCHAT_ADMIN_PIN"] = "1234"
db.DB_PATH = tmp_path / "jarvischat-skills.db"
SESSIONS.clear()
PIN_ATTEMPTS.clear()
RATE_EVENTS.clear()
db.init_db()
return TestClient(app.app, raise_server_exceptions=False)
def test_guest_can_list_skills(tmp_path: Path):
with make_client(tmp_path) as client:
sid = client.post("/api/auth/guest", headers={"Origin": "http://testserver"}).json()[
"session_id"
]
resp = client.get("/api/skills", headers={"X-Session-ID": sid, "Origin": "http://testserver"})
assert resp.status_code == 200
payload = resp.json()
assert payload["count"] >= 1
assert any(skill["key"] == "memory.search" for skill in payload["skills"])
def test_admin_can_toggle_skill_enabled_state(tmp_path: Path):
with make_client(tmp_path) as client:
login = client.post(
"/api/auth/login",
json={"pin": "1234"},
headers={"Origin": "http://testserver"},
)
sid = login.json()["session_id"]
headers = {"X-Session-ID": sid, "Origin": "http://testserver"}
disable = client.put(
"/api/skills/search.web",
json={"enabled": False},
headers=headers,
)
assert disable.status_code == 200
assert disable.json()["skill"]["enabled"] is False
active = client.get("/api/skills/active", headers={"X-Session-ID": sid, "Origin": "http://testserver"})
assert active.status_code == 200
assert all(skill["key"] != "search.web" for skill in active.json()["skills"])
def test_unknown_skill_update_is_rejected(tmp_path: Path):
with make_client(tmp_path) as client:
login = client.post(
"/api/auth/login",
json={"pin": "1234"},
headers={"Origin": "http://testserver"},
)
sid = login.json()["session_id"]
headers = {"X-Session-ID": sid, "Origin": "http://testserver"}
resp = client.put(
"/api/skills/nope.unknown",
json={"enabled": True},
headers=headers,
)
assert resp.status_code == 404
def test_prompt_injection_respects_skills_enabled_setting(tmp_path: Path):
with make_client(tmp_path):
conn = db.get_db()
try:
conn.execute(
"INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)",
("skills_enabled", "false"),
)
conn.commit()
without_skills = asyncio.run(build_system_prompt(conn, "", "hello"))
assert "## Active Skills" not in without_skills
conn.execute(
"INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)",
("skills_enabled", "true"),
)
conn.commit()
with_skills = asyncio.run(build_system_prompt(conn, "", "hello"))
assert "## Active Skills" in with_skills
assert "memory.search" in with_skills
finally:
conn.close()