In [ ]:


In [ ]:
import os, sys
try:
    from synapse.lib.jupyter import *
except ImportError as e:
    # Insert the root path of the repository to sys.path.
    # This assumes the notebook is located three directories away
    # From the root synapse directory. It may need to be varied
    synroot = os.path.abspath('../../../')
    sys.path.insert(0, synroot)
    from synapse.lib.jupyter import *

In [ ]:
# Create a cortex
core = await getTempCoreCmdr()
.. highlight:: none .. _storm-adv-vars: Storm Reference - Advanced - Variables ====================================== Storm supports the use of **variables.** A :ref:`gloss-variable` is a value that can change depending on conditions or on information passed to the Storm query. (Contrast this with a :ref:`gloss-constant`, which is a value that is fixed and does not change.) Variables can be used in a variety of ways, from providing simpler or more efficient ways to reference node properties, to facilitating bulk operations, to performing complex tasks or writing extensions to Synapse in Storm. These documents approach variables and their use from a **user** standpoint and aim to provide sufficient background for users to understand and begin to use variables. They do not provide an in-depth discussion of variables and their use from a fully developer-oriented perspective. - `Storm Operating Concepts`_ - `Variable Concepts`_ - `Variable Scope`_ - `Call Frame`_ - `Runtsafe vs. Non-Runtsafe`_ - `Types of Variables`_ - `Built-In Variables`_ - `User-Defined Variables`_ .. _op-concepts: Storm Operating Concepts ------------------------ When leveraging variables in Storm, it is important to keep in mind the high-level :ref:`storm-op-concepts`. Specifically: - Storm operations (e.g., lifts, filters, pivots, etc.) are performed on **nodes.** - Operations can be **chained** and are executed in order from left to right. - Storm acts as an **execution pipeline,** with each node passed individually and independently through the chain of Storm operations. - Most Storm operations **consume** nodes — that is, a given operation (such as a filter or pivot) acts upon the inbound node in some way and returns only the node or set of nodes that result from that operation. These principles apply to variables that reference nodes (or node properties) in Storm just as they apply to nodes, and so affect the way variables behave within Storm queries. .. _var-concepts: Variable Concepts ----------------- .. _var-scope: Variable Scope ++++++++++++++ A variable’s **scope** is its lifetime and under what conditions it may be accessed. There are two dimensions that impact a variable’s scope: its **call frame** and its **runtime safety** ("runtsafety"). .. _var-call-frame: Call Frame ++++++++++ A variable’s **call frame** is where the variable is used. The main Storm query starts with its own call frame, and each call to a "pure" Storm command, function, or subquery creates a new call frame. The new call frame gets a copy of all the variables from the calling call frame. Changes to existing variables or the creation of new variables within the new call frame do not impact the calling scope. Runtsafe vs. Non-Runtsafe +++++++++++++++++++++++++ An important distinction to keep in mind when using variables in Storm is whether the variable is runtime-safe (":ref:`gloss-runtsafe`") or non-runtime safe (":ref:`gloss-non-runtsafe`"). A variable that is **runtsafe** has a value independent of any nodes passing through the Storm pipeline. For example, a variable whose value is explicitly set, such as ``$string = mystring`` or ``$ipv4 = 8.8.8.8`` is considered runtsafe because the value does not change / is not affected by the specific node passing through the Storm pipeline. A variable that is **non-runtsafe** has a value derived from a node passing through the Storm pipeline. For example, a variable whose value is set to a node property value may change based on the specific node passing through the Storm pipeline. In other words, if your Storm query is operating on a set of DNS A nodes (``inet:dns:a``) and you define the variable ``$fqdn = :fqdn`` (setting the variable to the value of the ``:fqdn`` secondary property), the value of the variable will change based on the specific value of that property for each ``inet:dns:a`` node in the pipeline. All non-runtsafe variables are **scoped** to an individual node as it passes through the Storm pipeline. This means that a variable’s value based on a given node is not available when processing a different node (at least not without using special commands, methods, or libraries). In other words, the path of a particular node as it passes through the Storm pipeline is its own scope. The "safe" in non-runtsafe should **not** be interpreted as meaning the use of non-runtsafe variables is somehow "risky" or involves insecure programming or processing of data. It simply means the value of the variable is not safe from changing (i.e., it may change) as the Storm pipeline progresses. .. _var-types: Types of Variables ------------------ Storm supports two types of variables: - **Built-in variables.** Built-in variables facilitate many common Storm operations. They may vary in their scope and in the context in which they can be used. - **User-defined variables** User-defined variables are named and defined by the user. They are most often limited in scope and facilitate operations within a specific Storm query. .. _vars-builtin: Built-In Variables ++++++++++++++++++ Storm includes a set of built-in variables and associated variable methods (:ref:`storm-adv-methods`) and libraries (:ref:`storm-adv-libs`) that facilitate Cortex-wide, node-specific, and context-specific operations. Built-in variables differ from user-defined variables in that built-in variable names: - are initialized at Cortex start, - are reserved, - can be accessed automatically (i.e., without needing to define them) from within Storm, and - persist across user sessions and Cortex reboots. .. _vars-global: Global Variables ~~~~~~~~~~~~~~~~ Global variables operate independently of any node. That is, they can be invoked in a Storm query in the absence of any nodes in the Storm execution pipeline (though they can also be leveraged when performing operations on nodes). .. _vars-global-lib: $lib #### The library variable ( ``$lib`` ) is a built-in variable that provides access to the global Storm library. In Storm, libraries are accessed using built-in variable names (e.g., ``$lib.print()``). See :ref:`storm-adv-libs` for descriptions of the libraries available within Storm. .. _vars-node: Node-Specific Variables ~~~~~~~~~~~~~~~~~~~~~~~ Storm includes node-specific variables that are designed to operate on or in conjunction with nodes and require one or more nodes in the Storm pipeline. .. NOTE:: Node-specific variables are always non-runtsafe. .. _vars-node-node: $node ##### The node variable (``$node``) is a built-in Storm variable that **references the current node in the Storm query.** Specifically, this variable contains the inbound node’s node object, and provides access to the node’s attributes, properties, and associated attribute and property values. Invoking this variable during a Storm query is useful when you want to: - access the raw and entire node object, - store the value of the current node before pivoting to another node, or - use an aspect of the current node in subsequent query operations. The ``$node`` variable supports a number of built-in methods that can be used to access specific data or properties associated with a node. See the :ref:`meth-node` section of the :ref:`storm-adv-methods` document for additional detail and examples. .. _vars-node-path: $path ##### The path variable (``$path``) is a built-in Storm variable that **references the path of a node as it travels through the pipeline of a Storm query.** The ``$path`` variable is not used on its own, but in conjunction with its methods. See the :ref:`meth-path` section of the :ref:`storm-adv-methods` documents for additional detail and examples. .. _vars-trigger: Trigger-Specific Variables ~~~~~~~~~~~~~~~~~~~~~~~~~~ A :ref:`gloss-trigger` is used to support automation within a Cortex. Triggers use events (such as the creation of a node, setting the value of a node’s property, or applying a tag to a node) to fire ("trigger") the execution of a predefined Storm query. Storm uses a built-in variable specifically within the context of trigger-initiated Storm queries. .. _vars-trigger-tag: $tag #### Within the context of triggers that fire on ``tag:add`` events, the ``$tag`` variable represents the name of the tag that caused the trigger to fire. For example: You write a trigger to fire when any tag matching the expression ``#foo.bar.*`` is added to a ``file:bytes`` node. The trigger executes the following Storm command: .. parsed-literal:: -> hash:md5 [ +#$tag ] Because the trigger uses a wildcard expression, it will fire on any tag that matches that expression (e.g., ``#foo.bar.hurr``, ``#foo.bar.derp``, etc.). The Storm snippet above will take the inbound ``file:bytes`` node, pivot to the file’s associated MD5 node (``hash:md5``), and apply the same tag that fired the trigger to the MD5. See the :ref:`auto-triggers` section of the :ref:`storm-ref-automation` document and the Storm :ref:`storm-trigger` command for a more detailed discussion of triggers and associated Storm commands. .. _vars-csvtool: CSVTool-Specific Variables ~~~~~~~~~~~~~~~~~~~~~~~~~~ Synapse's **CSVTool** is used to ingest (import) data into or export data from a Cortex using comma-separated value (CSV) format. Storm includes a built-in variable to facilitate bulk data ingest using CSV. .. _vars-csvtool-rows: $rows ##### The ``$rows`` variable refers to the set of rows in a CSV file. When ingesting data into a Cortex, CSVTool reads a CSV file and a file containing a Storm query that tells CSVTool how to process the CSV data. The Storm query is typically constructed to iterate over the set of rows (``$rows``) using a "for" loop that uses user-defined variables to reference each field (column) in the CSV data. For example: .. parsed-literal:: for ($var1, $var2, $var3, $var4) in $rows { } See :ref:`syn-tools-csvtool` for a more detailed discussion of CSVTool use and associated Storm syntax. .. _vars-user: User-Defined Variables ++++++++++++++++++++++ User-defined variables can be defined in one of two ways: - At runtime (i.e., within the scope of a specific Storm query). This is the most common use for user-defined variables. - Mapped via options passed to the Storm runtime (i.e., when using the ``--optifle`` option from Synapse cmdr or via Cortex API access). This method is less common. When defined in this manner, user-defined variables will behave as though they are built-in variables that are runtsafe. .. _vars-names: Variable Names ~~~~~~~~~~~~~~ All variable names in Storm (including built-in variables) begin with a dollar sign ( ``$`` ). A variable name can be any alphanumeric string, **except for** the name of a built-in variable (see :ref:`vars-builtin`), as those names are reserved. Variable names are case-sensitive; the variable ``$MyVar`` is different from ``$myvar``. .. NOTE:: Storm will not prevent you from using the name of a built-in variable to define a variable (such as ``$node = 7``). However, doing so may result in undesired effects or unexpected errors due to the variable name collision. .. _vars-define: Defining Variables ~~~~~~~~~~~~~~~~~~ Within Storm, a user-defined variable is defined using the syntax: .. parsed-literal:: $ = The variable name must be specified first, followed by the equals sign and the value of the variable itself. ```` can be: - an explicit value / literal, - a node secondary or universal property, - a tag or tag property, - a built-in variable or method, - a library function, - a mathematical expression / "dollar expression", or - an embedded query. Examples ~~~~~~~~ Two types of examples are used below: - **Demonstrative example:** the ``$lib.print()`` library function is used to display the value of the user-defined variable being set. This is done for illustrative purposes only; ``$lib.print()`` is not required in order to use variables or methods. Keep Storm's operation chaining, pipeline, and node consumption aspects in mind when reviewing the demonstrative examples below. When using ``$lib.print()`` to display the value of a variable, the queries below will: - Lift the specified node(s). - Assign the variable. Note that assigning a variable has no impact on the nodes themselves. - Print the variable's value. - Return any nodes still in the pipeline. Because variable assignment doesn't impact the node(s), they are not consumed and so are returned (displayed) at the CLI. The effect of this process is that for each node in the Storm query pipeline, the output of ``$lib.print()`` is displayed, followed by the relevant node. - **Use-case example:** the user-defined variable is used in one or more sample queries to illustrate possible practical use cases. These represent exemplar Storm queries for how a variable or method might be used in practice. While we have attempted to use relatively simple examples for clarity, some examples may leverage additional Storm features such as subqueries, subquery filters, or flow control elements such as "for" loops or "switch" statements. *Assign a literal to a user-defined variable:* - Assign the value 5 to the variable ``$threshold``:

In [ ]:
# Define and print test query
q = '$threshold=5 $lib.print($threshold)'
q1 = '\n'
print(q + q1)
# Execute the query to test it and get the packed nodes (podes).
podes = await core.eval(q, num=0, cmdr=True)
- Tag any ``file:bytes`` nodes that have a number of AV signature hits higher than a given threshold for review:

In [ ]:
# Make some nodes
q = '[file:bytes=sha256:0000746c55336cd8d34885545f9347d96607d0391fbd3e76dae7f2b3447775b4 it:av:filehit=(sha256:0000746c55336cd8d34885545f9347d96607d0391fbd3e76dae7f2b3447775b4, (0bfef0179bf358f3fe7bad67fa529c77, trojan.gen.2)) it:av:filehit=(sha256:0000746c55336cd8d34885545f9347d96607d0391fbd3e76dae7f2b3447775b4, (325cd5a01724fa0c63907eac044f4961, trojan.agent/gen-onlinegames)) it:av:filehit=(sha256:0000746c55336cd8d34885545f9347d96607d0391fbd3e76dae7f2b3447775b4, (ac8d9645c6cdf123683a73a02e231052, w32/imestartup.a.gen!eldorado))]'
q1 = '[file:bytes=sha256:00007694135237ec8dc5234007043814608f239befdfc8a61b992e4d09e0cf3f it:av:filehit=(sha256:00007694135237ec8dc5234007043814608f239befdfc8a61b992e4d09e0cf3f, (be9793d772d23269ab0c165af819e74a, troj_gen.r002c0gkj17)) it:av:filehit=(sha256:00007694135237ec8dc5234007043814608f239befdfc8a61b992e4d09e0cf3f, (eef2ccb70945fb28a45c7f14f2a0f11d, malicious.1b8fb7)) it:av:filehit=(sha256:00007694135237ec8dc5234007043814608f239befdfc8a61b992e4d09e0cf3f, (ce4e34d2f9207095aa7351986bbad357, trojan-ddos.win32.stormattack.c)) it:av:filehit=(sha256:00007694135237ec8dc5234007043814608f239befdfc8a61b992e4d09e0cf3f, (ed344310e3203ec4348c4ee549a3b188, "trojan ( 00073eb11 )")) it:av:filehit=(sha256:00007694135237ec8dc5234007043814608f239befdfc8a61b992e4d09e0cf3f, (f5b5daeda10e487fccc07463d9df6b47, tool.stormattack.win32.10)) it:av:filehit=(sha256:00007694135237ec8dc5234007043814608f239befdfc8a61b992e4d09e0cf3f, (a0f25a5ba637d5c8e7c42911c4336085, trojan/w32.agent.61440.eii))]'
podes = await core.eval(q, num=4, cmdr=False)
podes = await core.eval(q1, num=7, cmdr=False)

In [ ]:
# Define and print test query
q = '$threshold=5 file:bytes +{ -> it:av:filehit } >= $threshold [ +#review ]'
print(q)
# Execute the query to test it and get the packed nodes (podes).
podes = await core.eval(q, num=1, cmdr=False)
*Assign a node secondary property to a user-defined variable:* - Assign the ``:user`` property from an Internet-based account (``inet:web:acct``) to the variable ``$user``:

In [ ]:
# Make a node
q = '[inet:web:acct=(twitter.com,bert) :email=bert@gmail.com]'
podes = await core.eval(q, num=1, cmdr=False)

In [ ]:
# Define and print test query
q = 'inet:web:acct=(twitter.com,bert) $user=:user $lib.print($user)'
q1 = '\n'
print(q + q1)
# Execute the query to test it and get the packed nodes (podes).
podes = await core.eval(q, num=1, cmdr=True)
- Find email addresses associated with a set of Internet accounts where the username of the email address is the same as the username of the Internet account:

In [ ]:
# Make another node
q = '[inet:web:acct=(twitter.com,ernie) :email=noternie@gmail.com]'
podes = await core.eval(q, num=1, cmdr=False)

In [ ]:
# Define and print test query
q = 'inet:web:acct $user=:user -> inet:email +:user=$user'
print(q)
# Execute the query to test it and get the packed nodes (podes).
podes = await core.eval(q, num=1, cmdr=False)
*Assign a node universal property to a user-defined variable:* - Assign the ``.seen`` universal property from a DNS A node to the variable ``$time``:

In [ ]:
# Make a node
q = '[inet:dns:a=(woot.com,1.2.3.4) .seen=("2018/11/27 03:28:14","2019/08/15 18:32:47")]'
podes = await core.eval(q, num=1, cmdr=False)

In [ ]:
# Define and print test query
q = 'inet:dns:a=(woot.com,1.2.3.4) $time=.seen $lib.print($time)'
q1 = '\n'
print(q + q1)
# Execute the query to test it and get the packed nodes (podes).
podes = await core.eval(q, num=1, cmdr=True)
.. NOTE:: In the example above, the raw value of the ``.seen`` property is assigned to the ``$time`` variable. ``.seen`` is an interval (:ref:`type-ival`) type, consisting of a pair of minimum and maximum time values. These values are stored in Unix epoch millis, which are the values shown by the output of the ``$lib.print()`` function.
- Given a DNS A record, find other DNS A records that pointed to the same IP address in the same time window:

In [ ]:
# Make some moar nodes
q = '[ ( inet:dns:a=(hurr.net,1.2.3.4) .seen=("2018/12/09 06:02:53","2019/01/03 11:27:01") ) ( inet:dns:a=(derp.org,1.2.3.4) .seen=("2019/09/03 01:11:23","2019/12/14 14:22:00"))]'
podes = await core.eval(q, num=2, cmdr=False)

In [ ]:
# Define and print test query
q = 'inet:dns:a=(woot.com,1.2.3.4) $time=.seen -> inet:ipv4 -> inet:dns:a +.seen@=$time'
print(q)
# Execute the query to test it and get the packed nodes (podes).
podes = await core.eval(q, num=2, cmdr=False)
*Assign a tag to a user-defined variable:* - Assign the explicit tag value ``cno.infra.anon.tor`` to the variable ``$tortag``:

In [ ]:
# Define and print test query
q = '$tortag=cno.infra.anon.tor $lib.print($tortag)'
q1 = '\n'
print(q + q1)
# Execute the query to test it and get the packed nodes (podes).
podes = await core.eval(q, num=0, cmdr=True)
- Tag IP addresses that Shodan says are associated with Tor with the ``#cno.infra.anon.tor`` tag:

In [ ]:
# Make some nodes
q = '[ inet:ipv4=84.140.90.95 inet:ipv4=54.38.219.150 inet:ipv4=46.105.100.149 +#rep.shodan.tor ]'
podes = await core.eval(q, num=3, cmdr=False)

In [ ]:
# Define and print test query
q = '$tortag=cno.infra.anon.tor inet:ipv4#rep.shodan.tor [ +#$tortag ]'
print(q)
# Execute the query to test it and get the packed nodes (podes).
podes = await core.eval(q, num=3, cmdr=False)
*Assign a tag timestamp to a user-defined variable:* - Assign the times associated with Threat Group 20’s use of a malicious domain to the variable ``$time``:

In [ ]:
# Make a node
q = '[inet:fqdn=evildomain.com +#cno.threat.t20.tc=(2015/09/08,2017/09/08)]'
podes = await core.eval(q, num=1, cmdr=False)

In [ ]:
# Define and print test query
q = 'inet:fqdn=evildomain.com $time=#cno.threat.t20.tc $lib.print($time)'
q1 = '\n'
print(q + q1)
# Execute the query to test it and get the packed nodes (podes).
podes = await core.eval(q, num=1, cmdr=True)
- Find DNS A records for any subdomain associated with a Threat Group 20 zone during the time they controlled the zone:

In [ ]:
# Make some moar nodes
q = '[ (inet:dns:a=(www.evildomain.com,1.2.3.4) .seen=(2016/07/12,2016/12/13)) (inet:dns:a=(smtp.evildomain.com,5.6.7.8) .seen=(2016/04/04,2016/08/02)) (inet:dns:a=(evildomain.com,12.13.14.15) .seen=(2017/12/22,2019/12/22))]'
podes = await core.eval(q, num=3, cmdr=False)

In [ ]:
# Define and print test query
q = 'inet:fqdn#cno.threat.t20.tc $time=#cno.threat.t20.tc -> inet:fqdn:zone -> inet:dns:a +.seen@=$time'
print(q)
# Execute the query to test it and get the packed nodes (podes).
podes = await core.eval(q, num=2, cmdr=False)
*Assign a tag property to a user-defined variable:* - Assign the risk value assigned by DomainTools to an FQDN to the variable ``$risk``:

In [ ]:
# Create a custom tag property
await core.core.addTagProp('risk', ('int', {'minval': 0, 'maxval': 100}), {'doc': 'Risk score'})

In [ ]:
# Make a node
q = '[inet:fqdn=badsite.org +#rep.domaintools:risk=85]'
podes = await core.eval(q, num=1, cmdr=False)

In [ ]:
# Define and print test query
q = 'inet:fqdn=badsite.org $risk=#rep.domaintools:risk $lib.print($risk)'
q1 = '\n'
print(q + q1)
# Execute the query to test it and get the packed nodes (podes).
podes = await core.eval(q, num=1, cmdr=True)
- Given an FQDN with a risk score, find all FQDNs with an equal or higher risk score:

In [ ]:
# Make some moar nodes:
q = '[ (inet:fqdn=stillprettybad.com +#rep.domaintools:risk=92) (inet:fqdn=notsobad.net +#rep.domaintools:risk=67)]'
podes = await core.eval(q, num=2, cmdr=False)

In [ ]:
# Define and print test query
q = 'inet:fqdn=badsite.org $risk=#rep.domaintools:risk inet:fqdn#rep.domaintools:risk>=$risk'
print(q)
# Execute the query to test it and get the packed nodes (podes).
podes = await core.eval(q, num=3, cmdr=False)
*Assign a built-in variable to a user-defined variable:* - Assign a ``ps:person`` node to the variable ``$person``:

In [ ]:
# Make a node
q = '[ps:person="0040a7600a7a4b59297a287d11173d5c"]'
podes = await core.eval(q, num=1, cmdr=False)

In [ ]:
# Define and print test query
q = 'ps:person=0040a7600a7a4b59297a287d11173d5c $person=$node $lib.print($person)'
q1 = '\n'
print(q + q1)
# Execute the query to test it and get the packed nodes (podes).
podes = await core.eval(q, num=1, cmdr=True)
- For a given person, find all objects the person "has" and all the news articles that reference that person (uses the Storm :ref:`storm-tee` command):

In [ ]:
# Make some moar nodes:
q = '[ (edge:has=((ps:person,0040a7600a7a4b59297a287d11173d5c),(inet:web:acct,(twitter.com,mytwitter)))) (edge:refs=((media:news,00076a3f20808a14cbaa01ad51111edc),(ps:person,0040a7600a7a4b59297a287d11173d5c)))]'
podes = await core.eval(q, num=2, cmdr=False)

In [ ]:
# Define and print test query
q = 'ps:person=0040a7600a7a4b59297a287d11173d5c $person = $node | tee { edge:has:n1=$person -> * } { edge:refs:n2=$person <- * +media:news }'
print(q)
# Execute the query to test it and get the packed nodes (podes).
podes = await core.eval(q, num=2, cmdr=False)
.. NOTE:: See the :ref:`meth-node` section of the :ref:`storm-adv-methods` document for additional detail and examples when using the ``$node`` built-in variable and related methods.
*Assign a built-in variable method to a user-defined variable:* - Assign the value of a domain node to the variable ``$fqdn``:

In [ ]:
# Make a node:
q = '[ inet:fqdn=mail.mydomain.com ]'
podes = await core.eval(q, num=1, cmdr=False)

In [ ]:
# Define and print test query
q = 'inet:fqdn=mail.mydomain.com $fqdn=$node.value() $lib.print($fqdn)'
q1 = '\n'
print(q + q1)
# Execute the query to test it and get the packed nodes (podes).
podes = await core.eval(q, num=1, cmdr=True)
- Find the DNS A records associated with a given domain where the PTR record for the IP matches the FQDN:

In [ ]:
# Make some moar nodes:
q = '[ inet:dns:a=(mail.mydomain.com,11.12.13.14) inet:dns:a=(mail.mydomain.com,25.25.25.25) ( inet:ipv4=25.25.25.25 :dns:rev=mail.mydomain.com ) ]'
podes = await core.eval(q, num=3, cmdr=False)

In [ ]:
# Define and print test query
q = 'inet:fqdn=mail.mydomain.com $fqdn=$node.value() -> inet:dns:a +{ -> inet:ipv4 +:dns:rev=$fqdn }'
print(q)
# Execute the query to test it and get the packed nodes (podes).
podes = await core.eval(q, num=1, cmdr=False)
*Assign a library function to a user-defined variable:* - Assign a value to the variable ``$mytag`` using a library function:

In [ ]:
# Define and print test query
q = '$mytag = $lib.str.format("cno.mal.sofacy") $lib.print($mytag)'
q1 = '\n'
print(q + q1)
# Execute the query to test it and get the packed nodes (podes).
podes = await core.eval(q, num=0, cmdr=True)
- Assign a value to the variable ``$mytag`` using a library function (example 2):

In [ ]:
# Make a node:
q = '[ file:bytes=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +#code.fam.sofacy ]'
podes = await core.eval(q, num=1, cmdr=False)

In [ ]:
# Define and print test query
q = 'file:bytes=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 for $tag in $node.tags(code.fam.*) { $malfam=$tag.split(".").index(2) $mytag=$lib.str.format("cno.mal.{malfam}", malfam=$malfam) $lib.print($mytag) }'
q1 = '\n'
print(q + q1)
# Execute the query to test it and get the packed nodes (podes).
podes = await core.eval(q, num=1, cmdr=True)
The above example leverages: - three variables (``$tag``, ``$malfam``, and ``$mytag``); - the :ref:`meth-node-tags` method; - the ``$lib.split()``, ``$lib.index()``, and ``$lib.str.format()`` library functions; as well as - a "for" loop.
- If a file is tagged as part of a malicious code (malware) family, then also tag the file to indicate it is part of that malware's ecosystem:

In [ ]:
# Define and print test query
q = 'file:bytes=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 for $tag in $node.tags(code.fam.*) { $malfam=$tag.split(".").index(2) $mytag=$lib.str.format("cno.mal.{malfam}", malfam=$malfam) [ +#$mytag ] }'
print(q)
# Execute the query to test it and get the packed nodes (podes).
podes = await core.eval(q, num=1, cmdr=False)
.. NOTE: The above query could be written as a **trigger** (:ref:`uto-triggers`) so that any time a ``#code.fam.`` tag was applied to a file, the corresponding ``#cno.mal.`` tag would be applied automatically.
*Use a mathematical expression / "dollar expression" as a variable:* - Use a mathematical expression to increment the variable ``$x``:

In [ ]:
# Define and print test query
q = '$x=5 $x=$($x + 1) $lib.print($x)'
q1 = '\n'
print(q + q1)
# Execute the query to test it and get the packed nodes (podes).
podes = await core.eval(q, num=0, cmdr=True)
- For any domain with a "risk" score from Talos, tag those with a score greater than 75 as "high risk":

In [ ]:
# Make some nodes:
q = '[ ( inet:fqdn=woot.com +#rep.talos:risk=36 ) ( inet:fqdn=derp.net +#rep.talos:risk=78 ) ( inet:fqdn=hurr.org +#rep.talos:risk=92 ) ]'
podes = await core.eval(q, num=3, cmdr=False)

In [ ]:
# Define and print test query
q = 'inet:fqdn#rep.talos:risk $risk=#rep.talos:risk if $($risk > 75) { [ +#high.risk ] }'
print(q)
# Execute the query to test it and get the packed nodes (podes).
podes = await core.eval(q, num=3, cmdr=False)
.. NOTE:: In the examples above, the mathematical expressions ``$($x + 1)`` and ``$($risk > 75)`` are not themselves variables, despite starting with a dollar sign ( ``$`` ). The syntax convention of "dollar expression" ( ``$( )`` ) allows Storm to support the use of variables (like ``$x`` and ``$risk``) in mathematical and logical operations.
*Assign an embedded query to a user-defined variable:* - TBD

In [ ]:
# Close cortex because done
_ = await core.fini()