feat: chat + optmz control panel
This commit is contained in:
@@ -106,7 +106,38 @@ assert d["decision"] == "DENIED"
|
||||
assert d["mode"] == "BLOCK"
|
||||
PY
|
||||
|
||||
# 5) Model OUTPUT still flows through the H4 output scan (no governance bypass):
|
||||
# 5) Gateway/cloud model failure falls back to the configured local model before
|
||||
# CASAN uses deterministic synthesis. This keeps an OmniRoute outage from taking
|
||||
# down an otherwise healthy local Ollama deployment.
|
||||
FALLBACK_CFG="$WORK/fallback-providers.json"
|
||||
cat > "$FALLBACK_CFG" <<'EOF'
|
||||
{"providers":{"gateway":{"model":"fake:gateway","class":"gateway","requires_preflight":true},"local":{"model":"fake:local","class":"local"}},"role_bindings":{"read_only":"gateway"}}
|
||||
EOF
|
||||
FALLBACK_STUB="$WORK/fallback-router.sh"
|
||||
cat > "$FALLBACK_STUB" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
OUT="$2"
|
||||
MODEL="${6:-}"
|
||||
if [[ "$MODEL" == "fake:gateway" ]]; then
|
||||
echo "gateway unavailable" >&2
|
||||
exit 2
|
||||
fi
|
||||
printf '{"text":"Local fallback answer.","input_tokens":31,"output_tokens":9}\n' > "$OUT"
|
||||
EOF
|
||||
chmod +x "$FALLBACK_STUB"
|
||||
CASAN_CHAT_MODEL_MODE=model CASAN_MODEL_PROVIDERS_FILE="$FALLBACK_CFG" CASAN_CHAT_MODEL_ROUTER="$FALLBACK_STUB" \
|
||||
python3 "$CHAT" ask --message "Summarize Plan 18 MVP-0 evidence" --actor alice --chat-id m5 > "$WORK/local-fallback.json"
|
||||
python3 - "$WORK/local-fallback.json" <<'PY' \
|
||||
&& pass "gateway failure falls back to local model" || fail "gateway-to-local fallback failed"
|
||||
import json, sys
|
||||
d = json.load(open(sys.argv[1]))
|
||||
assert d["success"] is True and d["synthesis"]["mode"] == "model", d
|
||||
assert d["synthesis"]["provider"] == "local", d["synthesis"]
|
||||
assert d["synthesis"]["fallback_from"] == "gateway", d["synthesis"]
|
||||
assert "Local fallback answer" in d["answer"], d["answer"]
|
||||
PY
|
||||
|
||||
# 6) Model OUTPUT still flows through the H4 output scan (no governance bypass):
|
||||
# a planted AWS key in the model text must be caught — the turn is DENIED
|
||||
# fail-closed and the raw secret never reaches the user.
|
||||
CASAN_CHAT_MODEL_MODE=model CASAN_CHAT_MODEL_ROUTER="$STUB" \
|
||||
|
||||
@@ -60,6 +60,27 @@ CASAN_OLLAMA_HOST="169.254.169.254:80" bash "$ROUTER" "$WORK/s.txt" "$WORK/s.jso
|
||||
RC=$?; set -e 2>/dev/null || true
|
||||
[[ "$RC" -ne 0 ]] && grep -q "endpoint_not_allowed" "$WORK/s.err" && pass "SSRF endpoint (metadata IP) rejected" || fail "SSRF endpoint not rejected (rc=$RC)"
|
||||
|
||||
# Docker Desktop may reach the operator's Mac-hosted Ollama only through one
|
||||
# explicit opt-in hostname. Other environment overrides must remain blocked.
|
||||
python - "$SCRIPTS/model-call.py" <<'PY'
|
||||
import importlib.util
|
||||
import os
|
||||
import sys
|
||||
|
||||
spec = importlib.util.spec_from_file_location("mc", sys.argv[1])
|
||||
mc = importlib.util.module_from_spec(spec); spec.loader.exec_module(mc)
|
||||
assert mc.OLLAMA_HOST == "127.0.0.1:11434"
|
||||
assert mc.DOCKER_OLLAMA_HOST == "host.docker.internal:11434"
|
||||
assert mc.ollama_host_allowed(mc.OLLAMA_HOST) is True
|
||||
assert mc.ollama_host_allowed(mc.DOCKER_OLLAMA_HOST) is False
|
||||
os.environ["CASAN_OLLAMA_HOST"] = "169.254.169.254:80"
|
||||
os.environ["CASAN_ALLOW_DOCKER_HOST_OLLAMA"] = "1"
|
||||
assert mc.ollama_host_allowed(os.environ["CASAN_OLLAMA_HOST"]) is False
|
||||
assert mc.ollama_host_allowed(mc.DOCKER_OLLAMA_HOST) is True
|
||||
print("ok")
|
||||
PY
|
||||
[[ $? -eq 0 ]] && pass "Docker Ollama bridge is a single explicit host" || fail "Docker Ollama bridge guard coverage failed"
|
||||
|
||||
# 5: no API-key / secret pattern leaked into logs.
|
||||
if grep -rEq 'sk-[A-Za-z0-9]{20}|Authorization: Bearer|AKIA[0-9A-Z]{16}' "$CASAN_STATE_ROOT/logs" 2>/dev/null; then
|
||||
fail "a secret/key pattern appears in .specify/logs"
|
||||
@@ -98,7 +119,8 @@ else
|
||||
skip "cloud-unavailable test (ANTHROPIC_API_KEY is set)"
|
||||
fi
|
||||
|
||||
# 8: cloud response parsers use provider token usage and fail closed on malformed
|
||||
# 8: cloud and explicitly allowlisted OpenAI-compatible gateway response parsers
|
||||
# use provider token usage and fail closed on malformed
|
||||
# payloads. This is deterministic: urllib is monkeypatched, so no API key or
|
||||
# network call is needed.
|
||||
python - "$SCRIPTS/model-call.py" <<'PY'
|
||||
@@ -113,6 +135,8 @@ spec = importlib.util.spec_from_file_location("mc", sys.argv[1])
|
||||
mc = importlib.util.module_from_spec(spec); spec.loader.exec_module(mc)
|
||||
os.environ["OPENAI_API_KEY"] = "test-openai-key"
|
||||
os.environ["ANTHROPIC_API_KEY"] = "test-anthropic-key"
|
||||
os.environ["CASAN_OPENAI_COMPATIBLE_API_KEY"] = "test-gateway-key"
|
||||
os.environ["CASAN_OPENAI_COMPATIBLE_BASE_URL"] = "http://127.0.0.1:20128/v1"
|
||||
|
||||
seen = []
|
||||
|
||||
@@ -128,7 +152,7 @@ class FakeResp:
|
||||
|
||||
def fake_urlopen(req, timeout):
|
||||
seen.append((req.full_url, dict(req.header_items()), json.loads(req.data.decode())))
|
||||
if "openai.com" in req.full_url:
|
||||
if "openai.com" in req.full_url or "127.0.0.1:20128" in req.full_url:
|
||||
return FakeResp({
|
||||
"choices": [{"message": {"content": "SAFE"}}],
|
||||
"usage": {"prompt_tokens": 11, "completion_tokens": 3},
|
||||
@@ -142,13 +166,25 @@ def fake_urlopen(req, timeout):
|
||||
|
||||
mc.urllib.request.urlopen = fake_urlopen
|
||||
op = mc.call_openai("gpt-test", "hello", "classify")
|
||||
gw = mc.call_openai_compatible("local-test", "hello", "classify")
|
||||
an = mc.call_anthropic("claude-test", "hello", "judge")
|
||||
assert op["text"] == "SAFE" and op["input_tokens"] == 11 and op["output_tokens"] == 3, op
|
||||
assert gw["text"] == "SAFE" and gw["input_tokens"] == 11 and gw["output_tokens"] == 3, gw
|
||||
assert an["text"] == "APPROVED" and an["input_tokens"] == 17 and an["output_tokens"] == 5, an
|
||||
assert seen[0][0] == "https://api.openai.com/v1/chat/completions", seen[0]
|
||||
assert seen[1][0] == "https://api.anthropic.com/v1/messages", seen[1]
|
||||
assert seen[1][0] == "http://127.0.0.1:20128/v1/chat/completions", seen[1]
|
||||
assert seen[2][0] == "https://api.anthropic.com/v1/messages", seen[2]
|
||||
assert seen[0][2]["temperature"] == 0 and seen[0][2]["max_tokens"] == 16, seen[0][2]
|
||||
assert "temperature" not in seen[1][2] and seen[1][2]["max_tokens"] == 16, seen[1][2]
|
||||
assert seen[1][2]["temperature"] == 0 and seen[1][2]["max_tokens"] == 16, seen[1][2]
|
||||
assert "temperature" not in seen[2][2] and seen[2][2]["max_tokens"] == 16, seen[2][2]
|
||||
|
||||
os.environ["CASAN_OPENAI_COMPATIBLE_BASE_URL"] = "http://169.254.169.254/v1"
|
||||
try:
|
||||
mc.openai_compatible_url()
|
||||
except SystemExit as exc:
|
||||
assert exc.code == 2, exc.code
|
||||
else:
|
||||
raise AssertionError("gateway metadata endpoint was accepted")
|
||||
|
||||
for fn, bad in (
|
||||
(mc.parse_openai_payload, {"choices": [{"message": {"content": "SAFE"}}]}),
|
||||
@@ -163,7 +199,7 @@ for fn, bad in (
|
||||
raise AssertionError(f"{fn.__name__} accepted malformed provider payload")
|
||||
print("ok")
|
||||
PY
|
||||
[[ $? -eq 0 ]] && pass "cloud provider responses parse real usage and reject malformed payloads" || fail "cloud provider parser coverage failed"
|
||||
[[ $? -eq 0 ]] && pass "cloud/gateway provider responses parse real usage and reject malformed payloads" || fail "cloud/gateway provider parser coverage failed"
|
||||
|
||||
# 9: deliberate failing primary route -> fallback through the REAL router (not exit 9).
|
||||
if [[ "$TUNNEL_UP" -eq 1 ]]; then
|
||||
|
||||
Reference in New Issue
Block a user