DNS security automation — write Starlark policies that run at query time on your nameservers.
ThreatScript is a security DSL built on Starlark (a sandboxed Python dialect). Scripts run inside dnsscienced at query time, giving you per-query control over DNS responses — blocking, redirecting, logging, or allowing based on domain reputation, client IP, query type, and custom logic.
block-malware.star
def main(query):
score = threat.score(query.domain)
if score > 80:
dns.nxdomain(query)
log.alert("blocked: " + query.domain + " (score=" + str(score) + ")")
return
dns.allow(query)
onedns script push block-malware.star --name "Block high-risk domains"
The script is pushed to all dnsscienced nodes and begins executing on the next query.
def main(query):
# query.domain — queried domain name (string)
# query.type — record type: "A", "AAAA", "MX", etc.
# query.client — client IP address (string)
# Your logic here...
dns.allow(query) # permit the query
# or
dns.nxdomain(query) # respond NXDOMAIN
# or
dns.drop(query) # silently drop
Every script must define a main(query) function.
Execution is bounded to 2ms per query. Scripts that exceed the time limit are skipped with a logged error.
| Function | Description |
|---|---|
| dns.allow(query) | Permit the query and pass to resolver |
| dns.nxdomain(query) | Return NXDOMAIN response |
| dns.drop(query) | Silently drop — no response sent |
| dns.blackhole(query) | Return 0.0.0.0 (sinkhole) |
| dns.redirect(query, "upstream:53") | Forward query to a specific upstream |
| Function | Description |
|---|---|
| threat.score(domain) | Risk score 0–100 for a domain |
| threat.score_ip(ip) | Risk score for a client IP address |
| threat.is_dga(domain) | True if domain appears algorithmically generated |
| threat.is_tunnel(domain) | True if domain shows DNS tunneling patterns |
| Function | Description |
|---|---|
| log.info(msg) | Informational log entry |
| log.warn(msg) | Warning log entry |
| log.alert(msg) | Alert-level log entry (triggers monitoring) |
def main(query):
if threat.is_dga(query.domain):
dns.nxdomain(query)
log.alert("DGA blocked: " + query.domain)
return
dns.allow(query)
def main(query):
if query.type == "TXT" and threat.is_tunnel(query.domain):
dns.drop(query)
log.alert("DNS tunnel blocked from " + query.client)
return
dns.allow(query)
BLOCKED_CIDRS = ["192.0.2.0/24", "203.0.113.0/24"]
def main(query):
for cidr in BLOCKED_CIDRS:
if net.contains(cidr, query.client):
dns.drop(query)
return
dns.allow(query)
def main(query):
score = threat.score(query.domain)
if score >= 90:
dns.drop(query) # highest risk: silent drop
elif score >= 75:
dns.nxdomain(query) # high risk: NXDOMAIN
log.alert("blocked " + query.domain)
elif score >= 50:
log.warn("suspicious: " + query.domain + " score=" + str(score))
dns.allow(query) # medium risk: log and allow
else:
dns.allow(query)
# Push a script (creates or replaces by ID)
onedns script push block-malware.star
# Specify a custom ID (default: filename without .star)
onedns script push my-policy.star --id production-block
# Remove a script
onedns script delete production-block
# Check execution stats (runs, errors, blocked queries)
onedns script stats
| Limit | Value |
|---|---|
| Max execution time per query | 2ms |
| No file system access | Scripts are fully sandboxed |
| No network calls from scripts | Use threat module for intel lookups |
| Language | Starlark (Python subset) |