Skyhigh Policy Code Essentials
The Skyhigh SWG (Cloud) solution leverages a scripting language–based policy engine called MOWGLI (MOdules With Gluing Rule Language Interpreter), also referred to as Policy Code. This topic provides an overview of MOWGLI, including its key concepts, terminology, and syntax. The Skyhigh Policy Code enables you to define policies that dynamically invoke modules provided by Skyhigh Security.
Skyhigh Policy Code
MOWGLI is the language interpreter itself, while Policy Code combines the MOWGLI interpreter with a collection of functions, procedures, and modules provided by Skyhigh Security. These components are used across Skyhigh products.
Policy Code Terminologies
The following table lists the basic Policy Code terminologies:
| Term | Description |
|---|---|
|
Routine |
A routine, often referred to as a rule set, is a subset of the policy containing all the code necessary to perform a particular task. For example, you might create a routine (or rule set) to implement URL category filtering. Routines can be invoked by other parts of the policy, allowing them to be used selectively and multiple times. They can also be nested within other routines. |
| Trigger | A trigger is an event that initiates the execution of the policy analysis. In Skyhigh SWG (Cloud SWG), triggers include Web.Request, Web.Response, and EmbeddedObject. The policy applies or skips logic depending on the trigger that starts execution. |
| Initial Trigger | Some triggers occur as children or subsets of other triggers. The initial trigger refers to the parent trigger that initiates a subsequent child trigger. For example, an EmbeddedObject trigger might be initiated as a child of an initial Web.Request trigger. |
| Module | A module is a compiled namespace provided by Skyhigh that includes a set of related functions, procedures, and object classes to deliver a specific set of capabilities. For example, the Net module provides object classes, functions, and procedures for handling network addresses, ranges, and subnets. |
| Procedure | Procedures are methods within modules that accept arguments to accomplish a task. For example, MWG.EnableProgressPage is a procedure that accepts a single MWG.ProgressPageSetting argument and enables progress pages for large downloads. Procedures do not return a value. |
| Function | Functions are methods within modules that accept arguments, perform a task, and return a result. For example, RegExJSi.Matches accepts a RegExJSi object and a STRING object, and searches the string using the regular expression, and returns a BOOLEANindicating whether a match was found. |
Native Object Types
This section describes the built-in object types supported by the MOWGLI interpreter. It does not cover additional object types supported by modules included in Policy Code implementations in Skyhigh products.
Simple Object Types
| Data type | Description | Supported Operators |
|---|---|---|
|
BOOLEAN |
A simple data type whose value must be either |
|
|
NUMBER |
Numbers can contain both integers and floating point values. They can accurately hold a signed integer value up to 2^52 (~4.5 quadrillion). Example: NUMBER x = -11.5 |
|
|
STRING |
A typical string value is a sequence (or array) of characters. String values must be surrounded by double quotes. Single quotes are not valid. Example: STRING x = "Hello world!" |
|
Complex Object Types
VECTOR<ItemType>
A vector is an ordered list (array) of objects, excluding VECTOR and MAP types. Vectors include built-in functions that allow you to manipulate both their elements and the vector itself. You can configure a vector as SORTED, which ensures its elements remain in sorted order. You can declare a vector as sorted at creation, or configure it later using the SetSortedfunction. To check whether a vector is sorted, use the IsSorted function.
Example:
// Declaring x as an unsorted vector
VECTOR<NUMBER> x = 1, 5, 2
x = Add (x, 0)
// x == 1, 5, 2, 0
// Declaring y as a sorted vector
VECTOR<STRING, SORTED> y = "aaa", "ccc"
y = Add (y, "bbb")
// y == "aaa", "bbb", "ccc"
BOOLEAN z = IsSorted (y)
// z == TRUE
y = SetSorted (y, FALSE)
z = IsSorted (y)
// z == FALSE
Built-in functions for VECTOR objects
| Function Name | Description / Example | Return Type | Parameters |
|---|---|---|---|
Add(VECTOR, ItemType) |
Adds a new element to the vector and then returns the full vector with the added element. Example: VECTOR<STRING> x = "term_1", "term_2" |
VECTOR<ItemType> |
VECTOR, ITEMTYPEAccepts a VECTOR and a parameter of the same type as the vector elements. For example, if the vector is of type NUMBER (e.g. VECTOR<NUMBER>), then the parameter must be a NUMBER. |
Get(VECTOR, NUMBER) |
Returns the element from the vector in the specified index position. Example: VECTOR<STRING> x = "term_1", "term_2" |
ItemType |
VECTOR, NUMBERAccepts a VECTOR and a NUMBER which must be a valid index within the vector. |
IsEmpty(VECTOR) |
Returns a boolean value indicating whether the vector is empty. Example: VECTOR<NUMBER> x = 3, 5 |
BOOLEAN |
VECTOR
Accepts only a |
IsSorted() |
Returns a boolean value indicating whether the vector is sorted. Example: VECTOR<STRING, SORTED> x = "aaa", "bbb" |
BOOLEAN |
VECTOR
Accepts only a |
Find(VECTOR, ItemType) |
Finds an element within the vector and returns the index of that element if found. If the element is not found, the function returns -1. Example: VECTOR<NUMBER> x = 1, 3, 5 |
NUMBER |
VECTOR, ItemTypeAccepts a VECTOR and an object of the same type contained within the vector. For example, a VECTOR<STRING> and a STRING. |
RemoveLast(VECTOR) |
Removes the last element from the vector and returns the result. Example: VECTOR<NUMBER> x = 1, 3, 5 |
VECTOR<ItemType> |
VECTOR
Accepts only a |
Remove(VECTOR, NUMBER) |
Removes the element in the provided index and returns the resulting vector. Example: VECTOR<STRING> x = "aaa", "bbb", "ccc" |
VECTOR<ItemType> |
VECTOR, NUMBERAccepts a VECTOR and a NUMBER that is a valid index within the vector. |
Set(VECTOR, NUMBER, ItemType) |
Set an item at the specified position in the vector to the provided value. This function does not add a new element; it only replaces an existing one. Example: VECTOR<STRING> x = "aaa", "bbb", "ddd" |
VECTOR<ItemType> |
VECTOR, NUMBER, ItemTypeAccepts a VECTOR, a NUMBER that must be a valid index within the vector, and an item of the same type contained in the vector. |
SetSorted(VECTOR, BOOLEAN) |
Sets a vector as either sorted or unsorted and returns the resulting vector. Example: VECTOR<NUMBER> x = 5, 3, 1 |
VECTOR<ItemType> |
VECTOR, BOOLEANAccepts a VECTOR and a BOOLEAN indicating whether to set the vector as sorted (TRUE) or unsorted (FALSE). |
Size(VECTOR) |
Returns a number indicating how many elements are in the vector. Example: VECTOR<STRING> x = "aaa", "bbb", "ccc" |
NUMBER |
VECTOR
Accepts only a |
MAP<KeyType, ValueType>
A MAP is a list of key-value pairs in which the key type can be NUMBER or STRING and the value type can be declared as any type except VECTOR or MAP.Several built-in functions allow you to manipulate both the map elements and the map itself.
For example:
MAP<STRING, NUMBER> x = ("length", 10), ("width", 20), ("height", 5)
Built-in functions for MAPs
| Function Name | Description / Example | Return Type | Parameters |
|---|---|---|---|
GET(KeyType) |
Returns the value associated with the provided key parameter. Example: MAP<STRING, NUMBER> x = ("length", 10), ("width", 20), ("height", 5) |
ValueType |
MAP, KeyType
Accepts a |
HasKey(KeyType) |
Returns a boolean result indicating whether the map has the provided key argument. Example: MAP<STRING, NUMBER> x = ("length", 10), ("width", 20) |
BOOLEAN |
MAP, KeyType
Accepts a |
IsEmpty() |
Returns a boolean result indicating whether the map is empty. Example: MAP<STRING, STRING> names = ("first", "Fred"), ("last", "Flintstone") |
BOOLEAN |
MAP
Accepts only a |
GetKeys() |
Returns a vector of all the keys in the map. Example: MAP<STRING, STRING> names = ("first", "Fred"), ("last", "Flintstone") |
VECTOR<KeyType> |
MAP
Accepts only a |
Remove(KeyType) |
Removes the provided key and its associated value from the map and returns the result. Example: MAP<STRING, NUMBER> x = ("length", 10), ("width", 20), ("height", 5) |
MAP<KeyType, ValueType> |
MAP, KeyType
Accepts a |
Set(KeyType, ValueType) |
Adds or replaces a key-value pair in the map. Example: MAP<STRING, NUMBER> x = ("length", 10), ("width", 20) |
MAP<KeyType, ValueType> |
MAP, KeyType, ValueType
Accepts a |
Size() |
Returns the number of key-value pairs in the map. Example: MAP<STRING, NUMBER> x = ("length", 10), ("width", 20) |
NUMBER |
MAP
Accepts only a |
Structure
Routines
A routine, often referred to as a rule set, is similar to a function or method in many programming languages. It is a section of policy code that groups related logic to accomplish a task. A ROUTINE does not have a return value. Every line of policy code in a customer’s policy resides within a routine.
Declaration
Routines are declared using the syntax ROUTINE <routine_name> ON (<triggers>) { ... }
where,
<routine_name>must be an alphanumeric name that begins with a letter. Do not include spaces, but you may use underscores.<triggers>indicates for which triggers the routine is evaluated. This can be the keywordANYindicating all triggers, or it can be a comma-separated list of trigger names in parentheses. For example,(Web.Request, Web.Response).- Everything between the curly braces
{...}is the policy code executed by the routine.
For example:
ROUTINE my_routine ON (Web.Request, EmbeddedObject) {
// My policy code goes here
}
Policy Structure
Routines can be nested within other routines. This functionality modularizes the policy and allows the following:
- You can define some components of the policy, while Skyhigh maintains others globally.
- You can compartmentalize their policy and evaluate conditions to control when specific parts of the policy execute.
- You can call routines multiple times to selectively or repeatedly execute portions of the policy.
- Skyhigh can also make centrally managed routines available for inclusion in your policies.
Review the partially expanded policy in the sample image below. Orange highlights routines controlled by Skyhigh, while green highlights routines managed locally. Skyhigh controls the root of the policy (left), which allows Skyhigh to apply the global policy both before and after the policy executes. On the right side, the policy calls tenant‑restriction routines that Skyhigh centrally manages. The policy uses conditional IF statements to control when routines are called. These routines are centrally managed by Skyhigh, so the policy does not need to build or maintain them. The policy simply CALL the routines whenever they are required.
Syntax
Comments
The MOWGLI interpreter allows allows you to include comments in your code by prepending them with//. Anything after the // is ignored by the interpreter. You can place comments on a separate line or at the end of a line of code.
For example:
// This is a comment and is ignored by the interpreter
NUMBER x = 5 // I can add a comment here explaining why I set the value to 5
MOWGLI does not currently support block comments, or commenting multiple lines of code with a single operator.
IF Statements
You will find IF statements to be extremely prevalent especially in the default policies. Each rule in the Skyhigh SWG policy is implemented as an IF statement. Below is the basic syntax of IF statements:
IF (conditional statement) THEN {
// Conditionally-executed statements go here
}
ELSE IF (conditional statement) THEN {
// Conditionally-executed statements go here
}
ELSE IF (conditional statement) THEN {
// Conditionally-executed statements go here
}
ELSE {
// Conditionally-executed statements go here
}
The IF statement can be followed by any number of ELSE IF statements and, optionally, an unconditional ELSE statement. If there is only one conditionally-executed command in an IF or ELSE block, then the curly braces are not required. Also, note that the parentheses around the conditional statements are optional.
For example:
// Enable progress pages if the User-Agent header starts with "Mozilla"
// Otherwise, enable data trickling.
IF MWG.Request.Headers.Get ("User-Agent").StartsWith("Mozilla") THEN {
MWG.EnableProgressPage (Skyhigh_Progress_Page_Settings)
} ELSE {
MWG.EnableDataTrickling(dataTricklingSetting)
}
Dot Notation
MOWGLI uses dot notation that will be familiar to anyone experienced with object‑oriented languages such as Java, Python, or C#. The MOWGLI implementation allows the object before the period to be sent as the first parameter to the function or procedure that appears after the period. This design makes the syntax easier to read.
For example:
NUMBER x = 10
STRING y = "x = " + ToString(x)
This results in the string y equaling "x = 10". The concatenation of the "x = " string and the value of x requires converting the number x to a STRING or else a type mismatch error occurs. The ToString(NUMBER) function does this conversion for us.
Using dot notation, you can rewrite this code as follows:
NUMBER x = 10
STRING y = "x = " + x.ToString()
In the above example, the number x is passed to the ToString(NUMBER) function as the first (and only) parameter. Because no parameters are listed in the parenthesis, they are optional. You can further simplify the code to:
NUMBER x = 10
STRING y = "x = " + x.ToString
Example 1: Simple Case
The above example is simple because the original code is easy to understand. It shows how the argument before the dot is passed as the first parameter to the function or procedure after the dot. In this case, we used the built‑in ToString function in MOWGLI.
Example 2: Complex Case
Consider a more complex example that shows the real value of this functionality. The argument before the dot is passed as the first parameter to the function or procedure after the dot, just as before. In addition, the dot can separate a function from its namespace. In this example, we use functions not native to MOWGLI but provided by Skyhigh in a policy code module.
These functions all exist in the MWG namespace, so they are called using MWG.<function_name> as you will see.
STRING original_source = MWG.Get (MWG.Headers (MWG.Request ()), "X-Forwarded-For")
The above line takes the value of the request header X-Forwarded-For and stores it in a string called original_source.
The MWG.Request() function returns an MWG.RequestResponse object. MWG.Headers accepts an MWG.RequestResponse object and returns an MWG.HeaderMap object. The MWG.Get function accepts both an MWG.HeaderMap and a STRING and returns a STRING.
Nesting multiple functions can quickly become confusing, but dot notation makes it much easier to follow
STRING original_source = MWG.Request.Headers.Get ("X-Forwarded-For")
This line produces the same result but is easier to read. Declaring the MWGnamespace once applies it to all functions in the command. Dot notation reduces parentheses by passing parameters implicitly. The command runs left to right: MWG.Request() executes first and returns an MWG.RequestResponse object. That object is passed to MWG.Headers(), which returns an MWG.HeaderMap. The MWG.HeaderMap is then passed to MWG.Get(), with "X-Forwarded-For" as the second parameter. The STRING returned by MWG.Get() is assigned to original_source.
Looping
MOWGLI supports two types of loops: fixed‑count loops and FOR loops that iterate through all elements in a VECTOR. Each type of loop provides two commands to control execution or termination. The BREAK command ends the current loop and exits immediately. The CONTINUE command ends the current iteration and starts the next one, if the loop is not already complete.
LOOP(x)
Using the LOOP command, MOWGLI is able to loop a fixed number of times through the nested code.
For example:
NUMBER x = 0
LOOP 10
x = x + 1
// x == 10 after loop completes
In this example, the LOOP command does not use parentheses around the number 10 because they are optional. The command nested in the loop also does not use curly braces, which are optional when there is only a single command. Here is an alternate way to write the same code:
NUMBER x = 0
LOOP (10) {
x = x + 1
}
// x == 10 after loop completes
FOR loops
A FOR loop will iterate through all the elements of a VECTOR. This has the common syntax of FOR <ItemType> IN VECTOR<ItemType> DO where <ItemType> is a variable of the same type contained within the VECTOR . In each iteration of the loop, the variable will be set to the next element in the VECTOR, and this will continue looping one time for each element in the list.
For example:
VECTOR<STRING> x = "aaa", "bbb", "ccc"
STRING y = ""
STRING i = ""
FOR i IN x DO {
y = y + i
}
// y == "aaabbbccc" after the loop completes.
NOTE: The iterator (iin this example) must be declared prior to theFORloop.
In the above example, y is initiated to be an empty string. In the first iteration of the loop, i is set to the first element of x which is "aaa". This is concatenated to the end of y which sets its value to "aaa". Then, the loop iterates again with i being set to the second element in the vector which is "bbb". That is then concatenated on the end of y setting its value to "aaabbb". Finally, the loop is iterated a third time with i being set the the last element in the vector which is "ccc" and results in a value of "aaabbbccc" for variable y.
BREAK and CONTINUE
The BREAK and CONTINUE commands can be used to control the execution of loops. The BREAK command will terminate the execution of the loop entirely.
For example:
NUMBER x = 1
NUMBER factorial = 1
NUMBER max = 4
LOOP 10 {
x = x + 1
factorial = factorial * x
IF x >= max THEN
BREAK
}
// factorial == 6
The code in this example iteratively calculates a factorial value multiplying 1 * 2 * 3 * 4... * 10. Each iteration of the loop increments x by 1 and then multiplies factorial by x. However, the IF statement will check whether x >= max (which is 3) and if so, it will BREAK the loop and terminate execution. Therefore, the factorial value is restricted to 1 * 2 * 3 = 6.
The CONTINUE command will terminate the execution of the current iteration of the loop and will skip to the next iteration. See the following example:
MAP<STRING, NUMBER> dimensions = ("length", 10), ("height", 25), ("width", 20)
NUMBER area = 1
NUMBER d
FOR d IN dimensions.GetKeys DO {
IF d = "height" THEN
CONTINUE
area = area * dimensions.Get(d)
}
// area == 200
In the above example, the loop will iterate for each dimension in the map. In each iteration it will multiply area by the current dimension. However, the IF statement will check whether the dimension name (or key) is "height", and, if so, it will CONTINUE which terminates the loop before area is multiplied by the dimension and then continues with the next dimension. Therefore, instead of area equaling 1 * 10 * 20 * 25 = 5,000, it ends up equaling 1 * 10 * 20 = 200.
Optional Parentheses
There are several situations in MOWGLI code where parentheses can be used but are not required.
The following are cases where parentheses are optional::
- After a function or procedure called without any arguments (except those passed by dot notation)
- Parentheses are optional around the condition(s) of an
IFstatement - Around the parameters of a built-in procedure like
CALL - In a
LOOP (NUMBER)statement

.drawio.png?revision=1)