Knowledge Base

SRL Guide

Secdit Rule Language reference for custom audit rules, finding output, expressions, variables, helpers, and examples.

What SRL Is

Secdit Rule Language is the scripting language used by Rule Manager for custom audit logic. SRL evaluates appliance configuration data, applies conditional logic, and emits pass, fail, or informational findings.

The editor metadata controls the execution scope. The SRL code decides what to inspect and what findings to emit.

if (this.status equals "enable") {
    addPassFinding();
} else {
    addFailFinding(
        "The setting is disabled.",
        "Enable the setting."
    );
}
Execution Model
  • Run Rule controls whether the rule runs once for the whole device or once per VDOM.
  • Configuration Section scopes this to a selected config section when one is provided.
  • Loop Configuration Section controls whether the rule iterates each edit entry within that section.
  • When looping is enabled, this is the current entry during each iteration.
  • When looping is disabled and a Configuration Section is set, this is the selected section node.
  • If the rule emits no explicit finding but finishes in a fail state in legacy-compatible paths, SRL can still emit an implicit fail finding.
Syntax Basics

Statements

// Single-line comment
/* Multi-line
   comment */

$value = "text";
let $name = "root";
$total += 1;
$counter++;
++$retries;
$counter -= 2;

if (this.name equals "root") {
    addPassFinding();
} else {
    addFailFinding("Root is missing.", "Create or restore the root entry.");
}
  • Block statements use braces: if (...) { ... }, foreach (...) { ... }.
  • Assignments support both let $var = ... and $var = ....
  • $var += ... appends text or adds numbers, depending on both operand types.
  • $var -= ... subtracts a numeric value from a numeric variable.
  • $var++, ++$var, $var--, and --$var are supported for writable numeric variables.
  • Prefix forms return the updated value. Postfix forms return the original value.
  • Strings may use double quotes, single quotes, or backticks.
  • Booleans are true and false.
  • Numbers may be integers or decimals.
  • null is not a general-purpose runtime value in SRL expressions. It is only special-cased for supported optional finding arguments.
Variables And Paths

SRL can read both variables and configuration paths.

$adminName = this.name;
$pathText = path(this);
$parentNode = parent(this);
$policyName = config.vdoms.root.firewall.policy.10.name;
$vendor = appliance.vendor;
$wanType = appliance.interfaces.wan1.network_type;
$fieldName = "status";
$dynamicValue = this.$fieldName;
  • this is the current scoped configuration node.
  • config is the normalized configuration tree.
  • appliance is the detected device/runtime scope passed into SRL for both audits and Test Rule runs.
  • Path segments are dot-separated.
  • Path interpolation is supported, for example config.system.interface.$ifaceName.ip.
  • If a path cannot be resolved, SRL treats it as unresolved rather than throwing a hard parse error. Most helper functions then treat it as empty/missing.
  • When a config node is converted to a string, SRL renders it in a FortiGate-style config block format.
Path Meaning Example
appliance.vendorNormalized vendor slugfortinet
appliance.typeDetected appliance type/platform familyfortigate
appliance.hardwareDetected hardware/platform stringFortiGate-90E
appliance.osDetected OS version7.2.10
appliance.os_major_minorMajor/minor version extracted from appliance.os7.2
appliance.scopeRuntime scope modeglobal, vdom, all
appliance.multi_vdomWhether more than one VDOM is selected for the runtrue
appliance.vdomsSelected/detected VDOM names for the run["root","wan-vdom"]
appliance.interfaces.any.network_typeBuilt-in catch-all network type tokenany
appliance.interfaces.<name>.network_typeDetected or classified network type for a specific interface. Common values include internet, internal, dmz, external, and restricted. If no value is available for that interface, SRL returns undefined.internet
Control Flow
if (this.status equals "enable") {
    addPassFinding();
} else if (this.status equals "disable") {
    addFailFinding("The setting is disabled.", "Enable the setting.");
} else {
    addInfoFinding("The setting could not be classified.", "Review the configuration manually.");
}

foreach ($member in this.member) {
    if ($member contains "all") {
        addFailFinding("The list contains ALL.", "Replace ALL with explicit members.");
        break
    }
}
  • Supported blocks: if, else if, elseif, else, foreach.
  • Supported flow control: return, break, continue.
  • foreach ($item in some.path) loops visible array items.
  • Inside a foreach, the loop variable and this are both set to the current item.
Operators
Operator Meaning Example
equals, ==, isCase-insensitive equalitythis.name equals "root"
not equals, !=, is notCase-insensitive inequalitythis.status != "enable"
containsSubstring or list membership matchthis.service contains "HTTPS"
not containsInverse contains checkthis.srcaddr not contains "all"
inValue is in the right-hand list"HTTPS" in this.service
exists, not existsPresence / absencethis.comment exists
empty, not emptyEmpty / non-emptythis.member not empty
matches_regex, not matches_regexRegular expression testthis.name matches_regex "^port[0-9]+$"
>, <, >=, <=Numeric comparisonnumber(this.timeout) > 15
and, &&Boolean ANDa and b
or, ||Boolean ORa or b
not, !Boolean NOTnot (this.status equals "enable")
+String concatenation or numeric addition"User: " + this.name
Built-In Functions

Text and conversion helpers

Function Purpose Example
lower(value)Lowercase textlower(this.status)
upper(value)Uppercase textupper(this.name)
trim(value)Trim surrounding whitespacetrim(this.comment)
string(value)Convert to stringstring(this)
number(value)Convert to numbernumber(this.timeout)
bool(value)Convert to booleanbool(this.enabled)
replace(pattern, replacement, subject)Literal or regex replacementreplace(" ", "-", this.name)
split(value, delimiter)Split text to a listsplit("a,b,c", ",")
join(list, delimiter)Join a list into textjoin(this.member, ", ")

Presence and counting

Function Purpose Example
count(value)Count visible array itemscount(this.member)
len(value)String length or array countlen(this.name)
exists(value)Check if a value existsexists(this.comment)
not_exists(value)Inverse exists checknot_exists(this.comment)
empty(value)Check for empty/missingempty(this.member)
not_empty(value)Check for non-emptynot_empty(this.member)
is_true(value)Truthy conversion testis_true(this.enabled)
is_false(value)Falsy conversion testis_false(this.enabled)

Numeric helpers

Function Purpose Example
number_gt(a, b)Greater thannumber_gt(this.timeout, 15)
number_gte(a, b)Greater than or equalnumber_gte(this.timeout, 15)
number_lt(a, b)Less thannumber_lt(this.timeout, 15)
number_lte(a, b)Less than or equalnumber_lte(this.timeout, 15)

Certificate helpers

Function Purpose Example
cert_days_remaining(certificateBase64)Returns the number of whole days remaining before a certificate expires. Returns empty/null when the certificate cannot be parsed.cert_days_remaining(this.certificate)

Collection helpers

Function Purpose Example
any(list, ...values)True if any candidate is presentany(this.service, "HTTP", "TELNET")
all(list, ...values)True if all candidates are presentall(this.service, "HTTP", "HTTPS")

Metadata helpers

Function Purpose Example
name(value)Entry name metadataname(this)
parent(value)Parent config nodeparent(this)
path(value)Full config pathpath(this)

Network and address helpers

Function Purpose Example
is_ip(value)Validate IP textis_ip("192.0.2.1")
is_cidr(value)Validate CIDR textis_cidr("10.0.0.0/24")
cidr_contains(cidr, ip)True if the CIDR contains the IPcidr_contains("10.0.0.0/24", "10.0.0.10")
network_includes(container, subject)True when every subject IP, CIDR, or range is fully included inside the container IP, CIDR, or range. Accepts single values or arrays and supports IPv4 and IPv6.network_includes(getAllIpAddresses($dos_policy.dstaddr), $dstip)
port_includes(containerPorts, subjectPorts)True when every subject port or port range is fully included inside the container ports. Supports single ports, ranges, arrays, and special values such as ALL.port_includes(getAllServicePorts($dos_policy.service), getAllServicePorts(this.service))
dos_policy_covers(dosPolicies, interfaceName, destinationRefs, destinationPorts)True when the enabled DoS policies for the given interface fully cover every referenced destination IP, CIDR, or range and all supplied destination ports. Works with either config.firewall.dos-policy or config.firewall.dos-policy6.dos_policy_covers(config.firewall.dos-policy, $srcintf, $policy.dstaddr, getAllServicePorts($policy.service))
getAddressType(value)Resolve an address object typegetAddressType(this.srcaddr)
getAllIpAddressNames(value)Expand referenced IP address object namesgetAllIpAddressNames(this.srcaddr)
getAllIpAddresses(value)Expand referenced IP address valuesgetAllIpAddresses(this.srcaddr)
getAllAddressFqdnNames(value)Expand referenced FQDN object namesgetAllAddressFqdnNames(this.dstaddr)
getAllAddressFqdns(value)Expand referenced FQDN valuesgetAllAddressFqdns(this.dstaddr)
getAllAddressGeographyNames(value)Expand geography object namesgetAllAddressGeographyNames(this.dstaddr)
getAllAddressDeviceNames(value)Expand device object namesgetAllAddressDeviceNames(this.dstaddr)
getAllAddressDevices(value)Expand device object valuesgetAllAddressDevices(this.dstaddr)
getAllServicePortNames(value)Expand referenced service object namesgetAllServicePortNames(this.service)
getAllServicePorts(value)Expand all referenced service portsgetAllServicePorts(this.service)
getAllServiceTcpPorts(value)Expand referenced TCP portsgetAllServiceTcpPorts(this.service)
getAllServiceUdpPorts(value)Expand referenced UDP portsgetAllServiceUdpPorts(this.service)
getAllServiceSctpPorts(value)Expand referenced SCTP portsgetAllServiceSctpPorts(this.service)
Finding Functions
addPassFinding();

addFailFinding(details, remediation, remediation_time, title_override, scope_override, section_override);

addInfoFinding(details, remediation, remediation_time, title_override, scope_override, section_override);
  • addPassFinding() does not accept any parameters.
  • details is required for fail and info findings.
  • remediation is required for fail findings and optional for info findings.
  • remediation_time is optional for fail and info findings.
  • If remediation_time is omitted, left blank, or set to null, runtime defaults it to 30.
  • title_override is optional. If omitted, the rule name is used.
  • scope_override is optional.
  • section_override is optional.

Examples

addPassFinding();

addFailFinding(
    "Administrative access is exposed to unrestricted sources.",
    "Restrict this policy to trusted management networks."
);

addFailFinding(
    "Password complexity is disabled.",
    "Enable password complexity requirements.",
    ,
    "Weak password policy"
);

addInfoFinding(
    "Traffic logging is enabled for this policy.",
    ,
    45,
    "Logging enabled"
);

addFailFinding(
    "The global admin profile allows insecure services.",
    "Restrict the allowed administrative services.",
    45,
    "Global admin exposure",
    "global",
    "config.system.admin"
);
When you need to skip an optional middle argument but still provide a later argument, both of these are supported: , , and null. For example, addFailFinding("...", "...", , "Title"), addFailFinding("...", "...", null, "Title"), and addInfoFinding("...", , 15, "Title").
Worked Examples

1. Check a single section value

if (number(this.admintimeout) <= 15 and number(this.admintimeout) > 0) {
    addPassFinding();
} else {
    addFailFinding(
        "Administrative timeout is greater than 15 minutes or not set correctly.",
        "Set admintimeout to 15 minutes or less."
    );
}

2. Loop through section entries

foreach ($admin in this) {
    if ($admin.status equals "disable") {
        addInfoFinding(
            "An administrator entry is disabled: " + name($admin),
            "Delete administrators from the system, if they are no longer required.",
            10,
            "Disabled administrator"
        );
    }
}

3. Check certificate expiry

$days = cert_days_remaining(this.certificate)

if (not_exists($days)) {
    addInfoFinding(
        "Certificate expiry could not be checked.",
        "Review the certificate manually and confirm it is valid and correctly imported.",
        15
    );
} else if ($days <= 0) {
    addFailFinding(
        "Certificate has expired.",
        "Renew or replace the expired certificate.",
        30
    );
} else if ($days <= 30) {
    addFailFinding(
        "Certificate expires soon.",
        "Renew or replace the certificate before it expires.",
        30
    );
}

4. Match public policy destinations and ports to DoS policy coverage

foreach ($srcintf in this.srcintf) {
    if (not any(lower(appliance.interfaces.$srcintf.network_type), "any", "internet")) {
        continue;
    }

    if (dos_policy_covers(config.firewall.dos-policy, $srcintf, this.dstaddr, getAllServicePorts(this.service))) {
        addPassFinding();
    } else {
        addFailFinding(
            "No enabled DoS policy on " + $srcintf + " fully covers " + getAllIpAddresses(this.dstaddr) + " for ports " + getAllServicePorts(this.service) + ".",
            "Create or expand a DoS policy so its dstaddr and service fully cover the exposed destination and ports."
        );
    }
}

5. Normalize text before comparison

$normalized = lower(trim(this.status));

if ($normalized equals "enable") {
    addPassFinding();
} else {
    addFailFinding(
        "The feature is not enabled.",
        "Enable the feature."
    );
}

4. Work with service object expansion

$tcpPorts = getAllServiceTcpPorts(this.service);

if (any($tcpPorts, "23", "2323")) {
    addFailFinding(
        "The policy permits Telnet-related TCP ports.",
        "Remove Telnet and use SSH instead.",
        20,
        "Insecure management service"
    );
}

5. Use appliance metadata in Rule Test or full audits

addInfoFinding(
    "Running on " + appliance.vendor + " " + appliance.type + " " + appliance.os,
    ,
    5,
    "Appliance context"
);

6. Use increment and decrement operators

$i = 1;
$before = $i++;
$after = ++$i;
$after--;
$after -= 2;

addInfoFinding(
    "before=" + $before + ", i=" + $i + ", after=" + $after,
    ,
    0,
    "Counter example"
);
Quick Reference
  • $var = expr assigns a variable.
  • $var += expr appends text or adds numbers.
  • $var -= expr subtracts a numeric value.
  • $var++, ++$var, $var--, and --$var increment or decrement writable numeric variables.
  • if (...) { ... } supports else if and else.
  • foreach ($item in path) { ... } loops array-like nodes.
  • this is the current scoped object.
  • config is the normalized configuration root.
  • appliance exposes runtime metadata such as vendor, OS version, scope, VDOMs, and interface network types.
  • return, break, and continue are supported.
Type Notes
  • string(array) renders config-like text for config nodes.
  • number(value) strips non-numeric characters before conversion.
  • bool(value) treats true, 1, yes, on, and enabled as true.
  • Equality and contains comparisons are case-insensitive.
  • len(value) returns string length for text and item count for arrays.
Regex Notes
  • matches_regex treats the right side as a regex pattern body.
  • replace() supports both literal replacement and regex replacement when the pattern uses a proper regex delimiter, for example /pattern/i.
Validation Rules
  • Pass findings must use addPassFinding() with no arguments.
  • Fail findings require non-empty details and remediation.
  • Info findings require non-empty details. remediation is optional.
  • remediation_time must be numeric, blank, or null.
  • Unterminated strings, unclosed blocks, and unsupported statements are compile errors.
  • Runtime errors are surfaced in Test Rule and audit output.
Authoring Tips
  • Write complete finding text inside the finding function itself.
  • Prefer explicit titles for reusable or looped findings.
  • Use helper functions like lower() and trim() before comparing vendor text.
  • Increment and decrement operators only work on writable numeric SRL variables, not on read-only inputs like this, config, or appliance.
  • Use Test Rule with a real config before saving production rule changes.
  • Keep remediation text action-oriented and specific to the detected issue.