Rationale: LLM opinion is weighted higher (70%) because it analyzes intent mismatch and request context. Method heuristics provide a safety baseline (30%).
The LLM receives this system prompt (risk.service.ts:33):
You are a security risk assessor for an AI agent gateway.You evaluate whether an agent's stated intent matches the HTTP request it is making.You must respond with valid JSON in this exact format:{"score": <number 0.0-1.0>, "explanation": "<one sentence>"}Score guidelines:- 0.0-0.2: Low risk — intent clearly matches a safe read operation- 0.2-0.5: Medium risk — minor mismatch or write operation with plausible intent- 0.5-0.8: High risk — significant mismatch or destructive operation- 0.8-1.0: Critical risk — clear intent mismatch, dangerous method, or suspicious patternsRespond ONLY with the JSON object. No other text.
The LLM receives the following context (risk.service.ts:70):
Agent stated intent: "Charge customer $5 for subscription renewal"Actual HTTP request:Method: POSTURL: https://api.stripe.com/v1/chargesBody: {"amount":500,"currency":"usd"}Assess whether the intent matches the request and provide a risk score.
Body is truncated to 500 characters to control token usage.
Baseline risk scores by HTTP method (risk.service.ts:52):
Method
Score
Rationale
DELETE
0.7
Destructive operation, high impact
PUT
0.5
Full resource replacement, medium impact
PATCH
0.4
Partial update, medium-low impact
POST
0.3
Create or action, varies by endpoint
GET
0.1
Read-only, low impact
HEAD
0.05
Metadata only, very low impact
OPTIONS
0.05
Discovery only, very low impact
Other
0.2
Unknown method, low-medium baseline
function methodBaseScore(method: string): number { switch (method.toUpperCase()) { case 'DELETE': return 0.7; case 'PUT': return 0.5; case 'PATCH': return 0.4; case 'POST': return 0.3; case 'GET': return 0.1; case 'HEAD': case 'OPTIONS': return 0.05; default: return 0.2; }}
The RISK_THRESHOLD determines which requests require approval:
# .envRISK_THRESHOLD=0.5 # 0.0-1.0
Validation enforced at startup (backend/src/config/env.ts:41):
RISK_THRESHOLD: (() => { const val = parseFloat(process.env.RISK_THRESHOLD || '0.5'); if (isNaN(val) || val < 0 || val > 1) { throw new Error('RISK_THRESHOLD must be a number between 0 and 1'); } return val;})(),
{ "intent": "List all GitHub repositories for user octocat", "method": "DELETE", "targetUrl": "https://api.github.com/repos/octocat/hello-world", "body": null}
LLM evaluation:
{ "score": 0.9, "explanation": "Intent claims to list repos but request is DELETE to specific repo"}
Result: Request blocked with 428 status and queued for approval.
Risk assessment runs after URL validation but before idempotency check (proxy.service.ts:399):Rationale: Risky requests are blocked before any expensive operations (credential decryption, API calls). This prevents resource exhaustion attacks.
When a request is blocked, the agent receives a 428 Precondition Required response:
{ "error": "Request requires human approval", "action_id": "550e8400-e29b-41d4-a716-446655440000", "risk_score": 0.85, "risk_explanation": "Intent claims to list repos but request is DELETE to specific repo", "status_url": "/status/550e8400-e29b-41d4-a716-446655440000"}
The agent should:
Extract the action_id
Poll GET /status/:action_id every 5-10 seconds
Wait for status to change from PENDING to APPROVED or DENIED
If APPROVED, call POST /proxy/execute/:action_id to execute
You can extend the system prompt with service-specific rules. Modify RISK_SYSTEM_PROMPT in risk.service.ts:33:
const RISK_SYSTEM_PROMPT = `You are a security risk assessor for an AI agent gateway....Additional rules:- Any Stripe charge > $100 USD requires approval (score >= 0.8)- GitHub repository deletions always require approval (score = 1.0)- Shopify order cancellations require approval if reason is not "customer_request" (score >= 0.7)Respond ONLY with the JSON object. No other text.`;
Modifying the system prompt may affect LLM behavior unpredictably. Test thoroughly in a non-production environment first.