feat: enforce layer0 gate and add tests
This commit is contained in:
@@ -15,6 +15,9 @@ import json
|
||||
import sys
|
||||
from typing import List, Optional
|
||||
|
||||
from layer0 import layer0_entry
|
||||
from layer0.shadow_classifier import ShadowEvalResult
|
||||
|
||||
from .tool import OracleAnswerTool
|
||||
|
||||
|
||||
@@ -79,6 +82,12 @@ async def main_async(args: Optional[List[str]] = None) -> int:
|
||||
parser = build_parser()
|
||||
ns = parser.parse_args(args=args)
|
||||
|
||||
# Layer 0: pre-boot Shadow Eval gate before any processing.
|
||||
routing_action, shadow = layer0_entry(ns.question)
|
||||
if routing_action != "HANDOFF_TO_LAYER1":
|
||||
_render_layer0_block(routing_action, shadow)
|
||||
return 1
|
||||
|
||||
tool = OracleAnswerTool(
|
||||
default_frameworks=ns.frameworks,
|
||||
use_local_only=ns.local_only,
|
||||
@@ -130,5 +139,33 @@ def main() -> None:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _render_layer0_block(routing_action: str, shadow: ShadowEvalResult) -> None:
|
||||
"""
|
||||
Minimal user-facing responses for Layer 0 decisions.
|
||||
- Catastrophic: fail closed, no details beyond refusal.
|
||||
- Forbidden: governance violation noted.
|
||||
- Ambiguous: ask for clarification.
|
||||
"""
|
||||
if routing_action == "FAIL_CLOSED":
|
||||
print("Layer 0: cannot comply with this request.", file=sys.stderr)
|
||||
return
|
||||
if routing_action == "HANDOFF_TO_GUARDRAILS":
|
||||
print(
|
||||
"Layer 0: governance violation detected (e.g., GitOps bypass or dashboard request).",
|
||||
file=sys.stderr,
|
||||
)
|
||||
if shadow.reason:
|
||||
print(f"Reason: {shadow.reason}", file=sys.stderr)
|
||||
return
|
||||
if routing_action == "PROMPT_FOR_CLARIFICATION":
|
||||
print(
|
||||
"Layer 0: request is ambiguous. Please add specifics before rerunning.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return
|
||||
# Unexpected action; default to refusal.
|
||||
print("Layer 0: unrecognized routing action; refusing request.", file=sys.stderr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -7,6 +7,9 @@ from dataclasses import asdict
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from layer0 import layer0_entry
|
||||
from layer0.shadow_classifier import ShadowEvalResult
|
||||
|
||||
from .orchestrator import WAFInsight, WAFIntelligence
|
||||
|
||||
|
||||
@@ -56,6 +59,12 @@ def run_cli(argv: List[str] | None = None) -> int:
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
# Layer 0: pre-boot Shadow Eval gate.
|
||||
routing_action, shadow = layer0_entry(f"waf_intel_cli file={args.file} limit={args.limit}")
|
||||
if routing_action != "HANDOFF_TO_LAYER1":
|
||||
_render_layer0_block(routing_action, shadow)
|
||||
return 1
|
||||
|
||||
path = Path(args.file)
|
||||
if not path.exists():
|
||||
print(f"[error] file not found: {path}", file=sys.stderr)
|
||||
@@ -130,3 +139,26 @@ def main() -> None:
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
def _render_layer0_block(routing_action: str, shadow: ShadowEvalResult) -> None:
|
||||
"""
|
||||
Minimal user-facing responses for Layer 0 decisions.
|
||||
"""
|
||||
if routing_action == "FAIL_CLOSED":
|
||||
print("Layer 0: cannot comply with this request.", file=sys.stderr)
|
||||
return
|
||||
if routing_action == "HANDOFF_TO_GUARDRAILS":
|
||||
reason = shadow.reason or "governance_violation"
|
||||
print(
|
||||
f"Layer 0: governance violation detected ({reason}).",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return
|
||||
if routing_action == "PROMPT_FOR_CLARIFICATION":
|
||||
print(
|
||||
"Layer 0: request is ambiguous. Please add specifics before rerunning.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return
|
||||
print("Layer 0: unrecognized routing action; refusing request.", file=sys.stderr)
|
||||
|
||||
@@ -20,6 +20,9 @@ from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple
|
||||
|
||||
from layer0 import layer0_entry
|
||||
from layer0.shadow_classifier import ShadowEvalResult
|
||||
|
||||
# Try to import sklearn, fall back to pure Python
|
||||
try:
|
||||
from sklearn.feature_extraction.text import TfidfVectorizer
|
||||
@@ -555,6 +558,11 @@ if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
|
||||
for text in test_inputs:
|
||||
routing_action, shadow = layer0_entry(text)
|
||||
if routing_action != "HANDOFF_TO_LAYER1":
|
||||
print(_layer0_cli_msg(routing_action, shadow), file=sys.stderr)
|
||||
continue
|
||||
|
||||
result = classifier.analyze(text)
|
||||
print(f"\nInput: {text[:50]}...")
|
||||
print(f" Label: {result['classification']['label']}")
|
||||
@@ -562,3 +570,14 @@ if __name__ == "__main__":
|
||||
print(f" Risk Level: {result['risk_level'].upper()}")
|
||||
print(f" Anomaly Score: {result['anomaly']['score']:.2%}")
|
||||
print(f" Recommendation: {result['anomaly']['recommendation']}")
|
||||
|
||||
|
||||
def _layer0_cli_msg(routing_action: str, shadow: ShadowEvalResult) -> str:
|
||||
if routing_action == "FAIL_CLOSED":
|
||||
return "Layer 0: cannot comply with this request."
|
||||
if routing_action == "HANDOFF_TO_GUARDRAILS":
|
||||
reason = shadow.reason or "governance_violation"
|
||||
return f"Layer 0: governance violation detected ({reason})."
|
||||
if routing_action == "PROMPT_FOR_CLARIFICATION":
|
||||
return "Layer 0: request is ambiguous. Please add specifics before rerunning."
|
||||
return "Layer 0: unrecognized routing action; refusing request."
|
||||
|
||||
@@ -15,6 +15,8 @@ sys.path.insert(0, '/Users/sovereign/Desktop/CLOUDFLARE')
|
||||
|
||||
from mcp.waf_intelligence.orchestrator import WAFIntelligence
|
||||
from mcp.waf_intelligence.analyzer import WAFRuleAnalyzer
|
||||
from layer0 import layer0_entry
|
||||
from layer0.shadow_classifier import ShadowEvalResult
|
||||
|
||||
|
||||
class WAFIntelligenceMCPServer:
|
||||
@@ -230,19 +232,26 @@ class WAFIntelligenceMCPServer:
|
||||
}
|
||||
print(json.dumps(response), flush=True)
|
||||
|
||||
elif message.get("method") == "tools/call":
|
||||
params = message.get("params", {})
|
||||
tool_name = params.get("name")
|
||||
tool_args = params.get("arguments", {})
|
||||
|
||||
result = self.handle_tool_call(tool_name, tool_args)
|
||||
|
||||
response = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": message.get("id"),
|
||||
"result": result
|
||||
}
|
||||
elif message.get("method") == "tools/call":
|
||||
params = message.get("params", {})
|
||||
tool_name = params.get("name")
|
||||
tool_args = params.get("arguments", {})
|
||||
|
||||
# Layer 0: pre-boot Shadow Eval gate before handling tool calls.
|
||||
routing_action, shadow = layer0_entry(_shadow_query_repr(tool_name, tool_args))
|
||||
if routing_action != "HANDOFF_TO_LAYER1":
|
||||
response = _layer0_mcp_response(routing_action, shadow, message.get("id"))
|
||||
print(json.dumps(response), flush=True)
|
||||
continue
|
||||
|
||||
result = self.handle_tool_call(tool_name, tool_args)
|
||||
|
||||
response = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": message.get("id"),
|
||||
"result": result
|
||||
}
|
||||
print(json.dumps(response), flush=True)
|
||||
|
||||
elif message.get("method") == "notifications/initialized":
|
||||
# Client acknowledged initialization
|
||||
@@ -277,3 +286,41 @@ class WAFIntelligenceMCPServer:
|
||||
if __name__ == "__main__":
|
||||
server = WAFIntelligenceMCPServer()
|
||||
server.run()
|
||||
|
||||
|
||||
def _shadow_query_repr(tool_name: str, tool_args: dict) -> str:
|
||||
"""Build a textual representation of the tool call for Layer 0 classification."""
|
||||
try:
|
||||
return f"{tool_name}: {json.dumps(tool_args, sort_keys=True)}"
|
||||
except TypeError:
|
||||
return f"{tool_name}: {str(tool_args)}"
|
||||
|
||||
|
||||
def _layer0_mcp_response(routing_action: str, shadow: ShadowEvalResult, msg_id: Any) -> dict:
|
||||
"""
|
||||
Map Layer 0 outcomes to MCP responses.
|
||||
Catastrophic/forbidden/ambiguous short-circuit with minimal disclosure.
|
||||
"""
|
||||
base = {"jsonrpc": "2.0", "id": msg_id}
|
||||
|
||||
if routing_action == "FAIL_CLOSED":
|
||||
base["error"] = {"code": -32000, "message": "Layer 0: cannot comply with this request."}
|
||||
return base
|
||||
|
||||
if routing_action == "HANDOFF_TO_GUARDRAILS":
|
||||
reason = shadow.reason or "governance_violation"
|
||||
base["error"] = {
|
||||
"code": -32001,
|
||||
"message": f"Layer 0: governance violation detected ({reason}).",
|
||||
}
|
||||
return base
|
||||
|
||||
if routing_action == "PROMPT_FOR_CLARIFICATION":
|
||||
base["error"] = {
|
||||
"code": -32002,
|
||||
"message": "Layer 0: request is ambiguous. Please clarify and retry.",
|
||||
}
|
||||
return base
|
||||
|
||||
base["error"] = {"code": -32099, "message": "Layer 0: unrecognized routing action; refusing."}
|
||||
return base
|
||||
|
||||
Reference in New Issue
Block a user