Security
The security resource exposes two halves of the platform’s security pipeline as API: vulnerabilities — known CVEs matched against each site’s installed plugins, themes, and core — and malware — scans and detections from the ClamAV engine, available per site and per environment. An account-wide rollup summarizes both across everything you own. 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 overview and malware scanning. This page covers reading and acting on that data over the API.
Authorization
Section titled “Authorization”| Action | Scope |
|---|---|
| Read malware overviews, scans, detections | security:read |
| Trigger a scan, restore or dismiss a detection | security:write |
| Read vulnerability findings | vulnerabilities:read |
security:write implies security:read, but vulnerabilities:read is its own
resource — security:read does not imply it. A monitoring key that should see
both halves needs both scopes, and the account rollup below requires both. As
everywhere, the down-scoping rule intersects a key’s scopes
against its owner’s role — a key can never see more than the human who minted it.
Vulnerabilities
Section titled “Vulnerabilities”GET /v1/sites/{siteID}/security/vulnerabilities
Open vulnerability findings matched against the site’s software inventory, sourced
from the Wordfence Intelligence vulnerability feed. The response has three
parts: a summary of open counts by severity, the findings themselves, and the
attribution notices.
{ "data": { "summary": { "critical": 1, "high": 0, "medium": 2, "low": 0, "total": 3, "checked_at": "2026-07-04T06:00:12Z", "inventory_status": "available" }, "findings": [ { "vuln_id": "8c4b…", "title": "Example Forms <= 3.1.4 - Unauthenticated SQL Injection", "component_type": "plugin", "component_name": "Example Forms", "component_slug": "example-forms", "installed_version": "3.1.2", "affected_range": "<= 3.1.4", "fixed_version": "3.1.5", "patched": false, "cvss_score": 9.8, "cvss_rating": "Critical", "cve": "CVE-2026-12345", "remediation": "Update to version 3.1.5 or newer.", "record_url": "https://www.wordfence.com/threat-intel/vulnerabilities/id/8c4b…" } ], "attribution": { "defiant_notice": "Vulnerability data © Defiant Inc. …", "defiant_license_url": "https://www.wordfence.com/…", "mitre_notice": "CVE data © MITRE Corporation …", "mitre_license_url": "https://cve.mitre.org/…" } }, "request_id": "req_01JAB1"}curl https://api.managed.dev/v1/sites/site_01J7Q2/security/vulnerabilities \ -H "Authorization: Bearer mfk_live_9aF2…" \ -H "Forge-Version: 2026-06-23"report, err := client.Security.ListVulnerabilities(ctx, "site_01J7Q2")mf security vulns --site site_01J7Q2Malware
Section titled “Malware”Malware scanning works at both levels: site paths under
/v1/sites/{siteID}/malware… cover the production site, and every endpoint has an
environment twin under /v1/sites/{siteID}/environments/{envID}/malware… for
scanning a staging or preview environment in isolation.
Method + path (…/malware) |
Scope | Returns / does |
|---|---|---|
GET …/malware |
security:read |
current overview — engine status, last scan, active detections |
GET …/malware/scans |
security:read |
scan history, newest first |
POST …/malware/scan |
security:write |
start an on-demand scan → 202 + a job |
POST …/malware/detections/{detID}/restore |
security:write |
restore a quarantined file (optional mark_ignored) |
POST …/malware/detections/{detID}/dismiss |
security:write |
dismiss a detection without restoring the file |
Each detection row carries the file path, the matched signature, a sha256,
and a status — detected, quarantined, quarantine_failed, restored,
ignored, or dismissed. Infected files are quarantined automatically; restore
and dismiss are how you resolve what the scanner caught.
Account rollup
Section titled “Account rollup”GET /v1/security/summary
One call, your whole security posture: for every site you own, open vulnerability
counts by severity (critical/high/medium/low plus vuln_total), the number
of active malware detections (malware_active), and the last scan’s timestamp and
outcome. It requires both scopes because it joins both halves. A
resource-pinned key gets a rollup scoped to its pin, and truncated is true when
your site set exceeds the per-request cap.
mf security summaryThe 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
malware.detectedorvulnerability.matchedwebhook arrives (both arecriticalseverity), or a scheduled scan surfaces a detection. Read the overview withGET …/malware. - Inspect. Each detection carries the flagged file’s
path, the matchedsignature, itssha256, and whether quarantine succeeded. The file is already quarantined — the site is safe while you decide. - Remediate. Either
POST …/detections/{detID}/restore— with{"mark_ignored": true}if it’s a false positive, so later scans don’t re-quarantine the same hash — orPOST …/detections/{detID}/dismissto accept the quarantine and move on. - 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-07-04-checkout"job, err := client.Security.TriggerMalwareScan(ctx, "site_01J7Q2")if err != nil { return err}if _, err := client.Jobs.WaitSuccess(ctx, job.ID, nil); err != nil { return err // a *forge.JobError if the scan job failed}mf security malware scan --site site_01J7Q2{ "data": { "id": "job_01J9SC", "type": "malware.scan", "status": "queued", "progress": 0, "created_at": "2026-07-04T01:02:00.110Z", "resource": { "type": "malware", "id": "site_01J7Q2", "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 flags a file that’s actually yours, restore it and stop future scans from re-quarantining it.
# read the overview — detections ride alongcurl https://api.managed.dev/v1/sites/site_01J7Q2/malware \ -H "Authorization: Bearer mfk_live_9aF2…" \ -H "Forge-Version: 2026-06-23"
# restore the file and ignore its hash going forwardcurl -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" \ -H "Content-Type: application/json" \ -d '{ "mark_ignored": true }'overview, err := client.Security.MalwareOverview(ctx, "site_01J7Q2")
// restore with markIgnored=true so later scans skip this hashdet, err := client.Security.RestoreMalwareDetection(ctx, "site_01J7Q2", "det_01JK4", true)mf security malware overview --site site_01J7Q2mf security malware restore-detection det_01JK4 --mark-ignored{ "data": { "id": "det_01JK4", "site_id": "site_01J7Q2", "source": "scan", "path": "wp-content/uploads/2026/06/custom-tool.php", "signature": "PHP.Backdoor.Generic-9831", "sha256": "9f2c…", "status": "restored", "detected_at": "2026-07-04T00:58:11Z", "updated_at": "2026-07-04T01:12:40Z" }, "request_id": "req_01JAB7"}Blocked requests Preview
Section titled “Blocked requests ”The Security → Blocks view — every request the WAF, rate limiter, Patchstack, or login lockout rejected — is a dashboard feature today. Block-list and block-volume endpoints are designed but not yet in the API; this section will document them when they ship.