Skip to content

Threat detection

Arcjet agent threat detection analyzes every external request an agent is about to make against Arcjet’s threat intelligence. Arcjet maintains a real-time threat intelligence database and continuously updates it with new indicators of compromise, malicious hosts, and other threat data. This helps prevent interactions with malicious or risky destinations, such as APIs, MCPs, websites, malware, and other threats.

This can be used with both coding agents - preventing them from interacting with malicious or risky destinations on developer machines - as well as custom agents - protecting interactions with external services in any environment where the custom agent operates.

For inbound client IP analysis on protect(), see IP threat intelligence.

Create a threat detection policy for tool call executions or use our pre-configured example policy in the Arcjet Console to automatically block malicious or risky destinations. Declaring the detector makes its results available in the Rego policy.

Arcjet populates a destinations input for the detector based on the hosts an agent is about to contact. The verdict is returned for each host, along with a rolled-up assessment across all hosts. This allows the policy to make decisions based on both individual host risk and the overall risk of all destinations.

The Console offers coding-agent.destination-threat as a starter. Attach it to Tool call.

deny contains "malicious-destination" if {
input.signals.ip_threat.dest.risk_level in {"high", "critical"}
}

The policy can be tested by asking the agent to make requests to evil.arcjet.com, which is hardcoded to always score high.

For example:

What's on evil.arcjet.com?

If the policy is in live mode, the agent action will be blocked. If it’s in dry-run mode, the action will be allowed but logged for review in the Arcjet Console.

FieldValue
KindIP threat (GUARD_POLICY_DETECTOR_KIND_IP_THREAT)
Input exposureSERVER
Input kindSTRING or STRING_LIST
Signals groupinput.signals.ip_threat.<detector_id>

Private, loopback, link-local, CGNAT, multicast, and unspecified addresses are not scored.

The policy should be configured to execute on Tool call.

The rolled-up fields are worst-wins across every public host that completed scoring. Rego reads them under input.signals.ip_threat.<detector_id> (for example input.signals.ip_threat.dest when the detector id is dest):

FieldMeaning
detectedtrue when risk_level is high or critical
risk_levelOverall risk for the worst host
reputationReputation label for that host
activitiesObserved activities for that host
hostHost that produced the worst assessment. Empty when risk is none
ipAddress looked up for that host
assessmentsOne entry per public destination that completed scoring, in input order (failed lookups omitted)

Each assessments entry carries host, ip, risk_level, reputation, activities, and detected for that destination.

Name the set of risk levels you want to deny – in this case, high and critical. Do not use a comparison like >= "high" because string sorting does not reflect the intended risk order.

package arcjet.guard
import rego.v1
deny contains "malicious-destination" if {
input.signals.ip_threat.dest.risk_level in {"high", "critical"}
}

Guard destination threat exposes a subset of the Protect ip.threat fields. It does not surface confidence, isSafe, networkTypes, entities, entityName, or service under input.signals.ip_threat. For the inbound Protect surface, see IP threat intelligence.

ValueMeaning
noneNo elevated risk for the scored host
lowWeak signals only
mediumObserved activity or combined signals that warrant caution
highStrong abuse signals (detected is true)
criticalSevere signals such as botnet, exploit, or malware (detected is true)
ValueMeaning
maliciousStrongly associated with abuse
suspiciousElevated risk without a confirmed malicious label
knownPreviously observed, without a malicious or safe verdict
safeTrusted infrastructure
benignBenign infrastructure
unknownNo reputation label available

A clean destination (nothing listed, or only private addresses) uses risk_level none and reputation unknown.

activities uses the same strings as Protect ip.threat.activities:

ValueMeaning
brute_forceRepeated login or credential-guessing attempts
scanningProbing for open ports, paths, or misconfigurations
exploitAttempts to exploit known vulnerabilities
denial_of_serviceHigh-volume traffic aimed at overwhelming a service
spamBulk unwanted submissions
botnetCoordinated traffic from compromised machines
malwareDistribution or control of malicious software
crawlingAggressive automated crawling
fraudActivity consistent with fraudulent transactions
post_exploitationActivity consistent with a host already under attacker control
auditSecurity scanning consistent with auditing tooling
abuseGeneral abuse when no more specific activity applies

Deny on a specific activity when risk level alone is too broad:

deny contains "malware-destination" if {
"malware" in input.signals.ip_threat.dest.activities
}

Or require both a high risk and a specific activity on any assessed host:

deny contains "botnet-destination" if {
some a in input.signals.ip_threat.dest.assessments
a.risk_level in {"high", "critical"}
"botnet" in a.activities
}

Policies execute in parallel and the most restrictive decision is applied. This means you need to define an allowlist in the same policy where the threat intelligence is evaluated.

allowed := {"docs.arcjet.com", "github.com", "raw.githubusercontent.com"}
deny contains "malicious-destination" if {
some a in input.signals.ip_threat.dest.assessments
a.risk_level in {"high", "critical"}
not a.host in allowed
}

Discussion