Quarantine
For: Platform operators and security teams managing automatic blocking of high-risk requests.
Operations Overview | Approval Workflows | Quarantine | Risk Scoring | CP Connectivity | Telemetry
A quarantine policy decision blocks the request immediately (HTTP 403) and records a quarantine entry server-side for operator review. Unlike deny, which stops the request with no follow-up, quarantine entries persist and can be investigated, released, or used to tune policy thresholds.
Table of Contents
- Quarantine vs Deny vs Approval
- Triggering quarantine in Policy
- Quarantine Entry Lifecycle
- Reviewing Quarantine Entries
- Releasing a Quarantine Entry
- Caller-Visible Response
- Admin API Reference
- Quarantine Entry Fields
- Prometheus Metrics
- Troubleshooting
- See Also
Quarantine vs Deny vs Approval
| Decision | HTTP | Server record | Operator action | Use when |
|---|---|---|---|---|
deny | 403 | Execution record only | None | Policy violation; no review needed |
quarantine | 403 | Quarantine entry + execution record | Review and optionally release | Suspicious activity; needs investigation |
require_approval | 202 | Approval record | Must approve/deny to proceed | Hold for human sign-off before executing |
Use quarantine when you want to block and flag for investigation. The caller receives 403 (same as deny) but the entry appears in the quarantine queue for your ops team.
Triggering quarantine in Policy
Add a rule with effect.decision: quarantine in any policy layer. The risk layer is the most common location:
risk: - id: risk.quarantine.high_score priority: 100 when: field: context.risk.score gt: "0.8" effect: decision: quarantine reason: "composite risk score exceeds threshold" - id: risk.quarantine.rate_spike priority: 90 when: all: - field: context.risk.signals operator: in value: "rate_spike" - field: context.risk.score gt: "0.6" effect: decision: quarantine reason: "rate spike detected — workload quarantined"
You can also quarantine from other layers:
# In the targets layer: block and flag requests to disallowed targets targets: - id: targets.quarantine.unknown_host priority: 50 when: field: action.target_host operator: not_in value: "api.openai.com,api.anthropic.com" effect: decision: quarantine reason: "request to unregistered target — requires review"
Quarantine Entry Lifecycle
- Quarantine entries do not expire automatically.
- Releasing an entry removes the quarantine flag for that workload/request pattern; it does not automatically allow future requests. Future requests are re-evaluated against policy.
- The entry record persists after release for audit purposes.
Reviewing Quarantine Entries
List active quarantine entries for an org:
curl -s "http://localhost:7080/v1/admin/quarantine?org_id=org-example-001&limit=20" \ -H "MVGC-Admin-Token: $MVGC_ADMIN_TOKEN" | jq .
Filter by workload:
curl -s "http://localhost:7080/v1/admin/quarantine?org_id=org-example-001&workload_id=wl-prod-app-1" \ -H "MVGC-Admin-Token: $MVGC_ADMIN_TOKEN" | jq .
Example entry:
{ "quarantine_id": "01955f3e-aaaa-7abc-8def-000000000001", "org_id": "org-example-001", "workload_id": "wl-prod-app-1", "caller_id": "service-abc", "caller_ip": "10.0.1.42", "action_type": "ai.infer", "reason": "composite risk score exceeds threshold", "risk_score": 0.87, "created_at": "2026-03-12T10:15:00Z", "released_at": null, "released_by": null }
The
quarantine_idis not included in the HTTP 403 response; the entry is written server-side. Use the list endpoint to find entries for a workload.
Releasing a Quarantine Entry
To release a specific entry after investigation:
QUARANTINE_ID="01955f3e-aaaa-7abc-8def-000000000001" curl -s -X POST "http://localhost:7080/v1/admin/quarantine/$QUARANTINE_ID/release" \ -H "MVGC-Admin-Token: $MVGC_ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"released_by": "ops-admin@example.com"}' | jq .
After release, the quarantine entry is marked with released_at and released_by. Subsequent requests from the workload are re-evaluated against policy: if the risk score has dropped below threshold, they will proceed normally.
Caller-Visible Response
The caller receives a standard 403 response. The quarantine_id is not exposed:
{ "decision": "quarantine", "reason": "composite risk score exceeds threshold", "record_id": "01955f3e-cccc-7abc-8def-000000000003" }
The caller cannot distinguish a quarantine 403 from a deny 403 by HTTP status alone. Use the decision field in the JSON response body to differentiate.
Admin API Reference
| Method | Path | Description |
|---|---|---|
GET | /v1/admin/quarantine | List quarantine entries. Query params: org_id, workload_id, limit |
POST | /v1/admin/quarantine/{id}/release | Release a quarantine entry. Body: {"released_by": "string"} |
All admin endpoints require the MVGC-Admin-Token header.
Quarantine Entry Fields
| Field | Description |
|---|---|
quarantine_id | UUIDv7: unique quarantine entry identifier |
org_id | Organisation that owns the quarantined request |
workload_id | Workload that triggered quarantine |
caller_id | Caller identifier from the request |
caller_ip | Source IP of the request as observed at the gateway |
action_type | Action type (e.g. ai.infer) |
reason | Policy reason code from the DSL rule's effect.reason |
risk_score | Composite risk score at the time of quarantine (if risk layer triggered it) |
created_at | When the quarantine entry was created |
released_at | When the entry was released (null if still active) |
released_by | Operator identity supplied on release (null if still active) |
Prometheus Metrics
| Metric | Type | Labels | Description |
|---|---|---|---|
mvgc_quarantine_total | Counter | org_id | Total quarantine decisions per org |
mvgc_requests_total{decision="quarantine"} | Counter | action_type, org_id | Quarantine decisions by action type |
Alert on a sudden increase in mvgc_quarantine_total to detect workloads behaving anomalously.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Quarantine entry not appearing in list | Wrong org_id filter | Check org_id in the request body that triggered the quarantine |
| Workload still quarantined after release | Risk score still above threshold; new requests re-evaluated | Investigate the underlying risk signals; adjust MVGC_RISK_* thresholds if needed |
| Caller gets 403 but no quarantine entry | Rule uses deny not quarantine | Check the policy rule's effect.decision field |
| High quarantine volume from one workload | Policy threshold too low for normal traffic | Review MVGC_RISK_RATE_THRESHOLD and MVGC_RISK_COST_THRESHOLD values |
See Also
- Risk Scoring — the signals that most commonly trigger quarantine
- Approval Workflows — use
require_approvalfor hold-not-block - Network Operations Guide — full policy DSL and decision reference
- Telemetry —
mvgc_quarantine_totalmetric and SIEMquarantineevents - Glossary — quarantine