Introduction to MQL

Learn MQL fundamentals including selectors, filters, assertions, error handling, and the interactive shell. A complete guide to querying and securing your infrastructure with Mondoo.

MQL (Mondoo Query Language) lets you query the configuration of any infrastructure asset, including cloud accounts, Kubernetes clusters, operating systems, SaaS services, and more, and write assertions that verify security and compliance. It's graph-based and declarative: you describe what you want to know, and MQL figures out how to retrieve and evaluate it.

This guide walks you through MQL from the ground up, covering syntax basics, selectors, filters, assertions, control structures, error handling, and the interactive shell, with runnable examples throughout.

Introduction

MQL has two core capabilities: querying infrastructure data and asserting that conditions are true.

Querying: Find all packages with "ssl" in their name and get their name and version:

packages.where( name == /ssl/i ) { name version }
packages.where.list: [
  0: {
    version: "3.5.4-1"
    name: "openssl"
  }
]

Asserting: Write checks that must evaluate to true. This check verifies that no user named "bob" exists on the system:

users.none( name == "bob" )
[ok] value: true

Installation

The recommended way to run MQL is through cnspec, Mondoo's CLI for creating and executing security policies.

Once you have cnspec installed, you can run the above query directly from your terminal:

cnspec run -c "packages.where( name == /ssl/i ) { name version }"
packages.where.list: [
  0: {
    version: "3.5.4-1"
    name: "openssl"
  }
]

Or you can open the interactive shell and paste queries directly:

cnspec shell

This will open:

→ connected to Arch Linux
  ___ _ __  ___ _ __   ___  ___
 / __| '_ \/ __| '_ \ / _ \/ __|
| (__| | | \__ \ |_) |  __/ (__
 \___|_| |_|___/ .__/ \___|\___|
   mondoo™     |_|
cnspec>

Then paste any MQL query at the prompt:

packages.where( name == /ssl/i ) { name version }
packages.where.list: [
  0: {
    version: "3.5.4-1"
    name: "openssl"
  }
]

In both cases, cnspec connects to your local operating system and runs the query against it.

To summarize, cnspec run gives you immediate CLI output, while cnspec shell opens an interactive session. For the rest of this guide, we show MQL queries without the wrapping command. You can run them either way. If you're just getting started, the shell is often easier because it gives you interactive feedback.

Alternative ways to run MQL

Besides cnspec, there are other ways to run MQL queries.

cnquery

cnquery is the query-only tool that cnspec is built on. It supports the same MQL language and providers but focuses on data retrieval rather than policy enforcement. You can run all the examples in this guide with cnquery as well:

cnquery run -c "packages.where( name == /ssl/i ) { name version }"

MQL policies

Policies wrap MQL queries and assertions in a structured format that adds titles, authors, versions, remediation guidance, and more. To learn about policies, see the policy guide.

We recommend learning the MQL basics on this page before exploring policies.

Embedded MQL

You can also embed the MQL runtime directly into your own applications. See the Embedding MQL guide for details.

Basic structure

MQL is a type-safe, compiled language that combines ideas from GraphQL and lightweight scripting.

  • It has base types like string, number, boolean, array, map, and resource. See Types below.
  • It compiles your code before execution, catching errors early. In practice, compilation is instant and transparent.
  • It uses GraphQL-style blocks for extracting structured data from resources.
  • It includes scripting features for transforming data and writing assertions.

Anatomy of MQL

MQL queries are often short code snippets that either extract data or contain an assertion. Here is an example:

MQL Anatomy

  • Resource: An object containing information about a component in your infrastructure or code. In this example we use the packages resource to access the operating system's list of packages. See more below.
  • Functions: Calls that perform operations on resources. In this example, where filters the list of packages.
  • Function call: The parentheses attached to a function, containing its arguments. Here the argument is a filter that selects only packages whose name matches the regular expression.
  • Fields: Contain pieces of information about a resource. Here we use the name field of each package both in the where filter and in the block call at the end, along with the version field.
  • Value: Literal values used for filtering, comparisons, and assertions. For example, /ssl/i is a case-insensitive regular expression that matches "SSL", "ssl", "sSl", and so on.
  • Block: The curly-brace section at the end that specifies which fields to extract. Everything before the block produces a filtered list of packages. The block then extracts the name and version from each package. Learn more in Blocks below.

Comments

MQL supports # comments, which are compatible with YAML policy files and query packs.

"hi" # I am a comment

MQL also supports // comments.

Types

MQL comes with a set of core types:

  • String: A normal string like "Hello!"
  • Number: Can represent an integer like 123 or a float like 1.23
  • Bool: For true and false
  • Regex: For matching regular expressions like /my.*re/i
  • Array: A list of values like [1,2,3]
  • Map: Key-value pairs like {a: 23}
  • Time: To represent time like time.now or time.today
  • Version: Often used for software and package versions like version("1.2.3-rc1")
  • IP: For IP addresses, supporting IPv4 ip("1.2.3.4/24") and IPv6 ip("2001:db8::1")

There are also a few special keywords and types:

  • Null: Reserved for the null value when something isn't set
  • Empty: Can be used to check if something is empty (e.g. empty arrays, empty strings, empty maps, or null values)

There is also a special dict type for representing JSON data. Because JSON types aren't known at compile time, MQL uses dict to handle dynamically typed values. You'll encounter it when working with JSON files, for example parse.json("my.json").params. See Dicts for more.

Schemas

Beyond built-in types, MQL interacts with real infrastructure through providers. A provider is a plugin that connects MQL to a specific platform and exposes its data as queryable resources.

If you've been following the examples above, you've already used one: running cnspec shell without arguments connects through the os provider to your local system.

For example, if you are connected to your local OS, you can use resources like:

users
packages
services

If you connect to a cloud environment like AWS, Azure, or Google Cloud, different resources become available:

aws.ec2.instances

For a full list of providers and their resources, see the resource reference. To learn about managing providers, see the providers guide.

UTF-8 encoding

All MQL code is encoded in UTF-8 to support arbitrary characters from other languages, symbols, or even emojis:

"UTF-8 in 日本 and 中文 👍".find(regex.emoji)
find: [
  0: "👍"
]

Resources and fields

A resource is a source of information about an asset in your infrastructure. These are examples of resources:

  • A user on the asset
  • The operating system running on the asset
  • An AWS S3 bucket
  • A Google Cloud compute instance
  • A Terraform state file
  • A Microsoft Entra ID domain

Each resource has one or more fields, pieces of information you can request from the resource. These are examples of fields:

  • A user can have a unique ID, a group, an SSH key, and more.
  • An operating system can have a name, a path, and more.
  • An AWS S3 bucket can have a version, can be public or not public, have encryption information, and more.
  • A Google Cloud compute instance can have deletion protection on or off, a hostname, and more.
  • A Terraform state file can have output values, modules, and more.
  • A Microsoft Entra ID domain can have a verified or unverified status, an authentication type, and more.

This example requests the platform of an asset. asset is the resource and platform is the field:

asset.platform

The output would be redhat, windows, k8s-pod, or similar.

You can chain fields to access related resources:

sshd.config.file
=> sshd.config.file: file path="/etc/ssh/sshd_config" size=1197 permissions.string="-rw-------"

sshd.config.file.path
=> sshd.config.file.path: "/etc/ssh/sshd_config"

Child resources

Some resources have child resources. For example, the aws.ec2 resource has multiple child resources, including aws.ec2.volume, aws.ec2.snapshot, and more.

Blocks

Blocks are a convenient way to group and extract information. They save you the trouble of repeating multiple requests for fields from one resource.

Instead of making individual requests like this:

sshd.config.file
sshd.config.params
sshd.config.ciphers

You can combine them into a block:

sshd.config {
  file
  params
  ciphers
}

The output is the same.

Incorrect:

command("ip6tables -L") {
  stdout.contains("Chain INPUT (policy DROP)")
  stdout.contains("Chain OUTPUT (policy DROP)")
}

Correct:

command("ip6tables -L") {
  inputPolicyDrop = stdout.contains("Chain INPUT (policy DROP)")
  chainPolicyDrop = stdout.contains("Chain OUTPUT (policy DROP)")
  inputPolicyDrop
  chainPolicyDrop
}

This requirement applies only to policies you plan to deploy in the Mondoo Console. If you're writing ad hoc policies or running queries in cnquery or cnspec, you don't need helper variables inside blocks.

Nest blocks

You can nest blocks:

sshd.config {
  file {
    path
    permissions
  }
}

Request all fields from a resource

A quick way to request all fields from a resource is by using { * }. For example, this requests all fields from the services resource:

services { * }

This expands all immediate fields of the given resource. It does not cascade to list any child resources.

Lists

Some resources provide information in lists. For example, this requests a list of users, a list of packages, and a list of services:

users
packages
services

Using blocks, you can access specific field values from every item in a list. For example, this requests the name, uid, and home field values for each result in a list of users:

users {
  name
  uid
  home
}

Basic functions

Functions let you filter, transform, and assert conditions on resources and fields. Some of the most commonly used functions operate on lists, including where, all, none, and more.

users.where( uid >= 1000 ) {
  name
  uid
}

To learn more, read Functions.

Control structures

MQL provides these control structures:

If

A simple if statement:

if( x > 0 ) {
  return y
}

You can also chain statements with else if and else:

if( x > 10 ) {
  return 1
} else if( x > 0 ) {
  return 0
} else {
  return -1
}

Switch

For multiple conditions, use switch:

switch( x ) {
case _ > 10:
  return 1
case _ > 0:
  return 0
default:
  return -1
}

MQL evaluates the cases from top to bottom until it finds a match. There is no automatic fall-through.

Conditional operators

MQL supports these conditional operators:

  • ==
  • !=
  • >
  • <
  • >=
  • <=
VALUE.inRange(MIN,  MAX)

Examples:

3.inRange(3, 5)

password.length.inRange(9, 15)

Although MQL is type-safe, it handles type coercion automatically so you don't have to worry about explicit conversions. For example, comparing a number to a string works as expected:

a = 2
b = "2"

a == 2 && b == 2

Here's a real-world example:

sshd.config.params["Port"] == 22

params is a map of strings, so values like Protocol and Port are strings, even though they look like numbers.

More examples of automatic type coercion:

"2" == 2

"2" == 2.0

"3" > 2

[1] == 1

This also simplifies regular expression comparisons:

"Hello world" == /H.*o/

To learn about conditional operators with lists, see Arrays.

To learn about conditional operators with maps, see Maps.

Functions

Parameters

Many fields take unnamed parameters by default:

sshd.config( "/path/to/my/sshd" )

You can also use named parameters to initialize resources:

parse.json(
  command('lsblk --json').stdout
)

Anonymous functions

Many functions accept an expression as their argument. For example, where takes a condition that is evaluated against each item in the list:

users.where( uid >= 1000 )

Inside the expression, field names like uid refer to the current item (in this case, each user in the list).

You can combine these with global resources and variables:

users.where( name == regex.email )

Some functions support both embedded and static values:

[1,2,3].contains( 3 )
[1,2,3].contains( _ > 2 )

Named arguments in functions

You can assign a name to the current item in a function expression. This is especially useful when nesting multiple levels, where it's otherwise unclear which resource a field belongs to:

users.all(user:
  groups.contains(group:
    user.uid == group.gid
  )
)

Data

This section covers MQL's data types in detail, including how to work with strings, numbers, arrays, maps, and JSON data.

Basic data types

MQL's basic data types are:

s1 = "I am a string"
s2 = 'I am also a string'
re = /Reg.* Expression/

n1 = 1.0 + 2

n  = null
b  = true || false

Regular expressions

The regex resource provides pre-built patterns for common formats. Here are a few examples:

"anya@forger.com" == regex.email

"10.0.0.255" == regex.ipv4
"fe80::1042:2c47:b787:f6bb" == regex.ipv6

"4832500902091714" == regex.creditCard

To learn about all the pre-built expressions, read the regex resource reference.

Time

MQL includes built-in time functions for working with dates and durations:

time.now
# 2022-10-13 14:42:35 -0700 PDT

time.now - time.day
# 2022-10-12 14:42:35 -0700 PDT
# subtracts a day from the current time

time.now - 2*time.hour
# 2022-10-13 11:42:35 -0700 PDT
# subtracts 2 hours from the current time

parse.date("2022-10-12T14:42:35Z")
# 2022-10-12 14:42:35 +0000 UTC
# uses RFC3339 layout by default
DATE.inRange(MIN, MAX)

Example:

time.now.inRange(time.now - time.day, time.now + time.day)

MQL can also parse durations:

parse.duration("3days")

parse.duration("1y")

Although the parser is very lenient, for best results, use:

30s = 30 seconds
1m = 1 minute
3h = 3 hours
90d = 90 days
5y = 5 years

Empty

The empty data type saves you the trouble of checking for different kinds of empty values, such as:

  • []
  • null
  • ''
  • {}

For example, this query finds any type of empty value:

users.list == empty

Semantic versioning

Use the semver type for semantic versioning. Create a semver using the semver keyword, which takes a string as an argument:

semver('3.12.1')

You can compare a semver with another semver or with a string:

semver('1.2.3') < semver('2.3')

semver('1.10') >= '1.2'

Arrays

Many resources contain lists of entries, like this example:

users {
  name
  uid
}

You can filter these lists using the where clause:

users.where( uid >= 1000 ) {
  name
  uid
}

Array assertions

To avoid unnecessary loops, MQL provides some keywords that make assertions on lists a lot simpler. For example:

users.all( uid >= 0 )

When an assertion fails, MQL prints the elements that caused the failure:

> users.all( uid > 0 )
[failed] users.all()
  actual:   [
	0: user id = user/0/root
  ]

The available assertions for all lists are:

users.all( name != "anya" )   <= make sure no user is called anya
users.one( name == "anya" )   <= one user must exist, but no more than one
users.none( name == "anya" )  <= no user exists with the name anya
users.contains( uid >= 1000 ) <= contains one or more users with uid >= 1000

For lists of strings, you can use the in assertion, which is the inverse of contains:

"anya".in(["abel","amos","anya"])

An ideal use for in is to combine it with properties. For example, if you define a property named allowedCiphers, you can assert that a configured cipher is in that list:

sshd.config.ciphers.in( props.allowedCiphers )

Another useful assertion for lists of strings is containsAll:

["abel","amos","anya"].containsAll(["abel","amos"])

Mapping fields

When you use a block to extract fields from a list, MQL returns an array of maps:

> users { name }
[
  0: { name: "root" }

  ...

]

You can map these values into a simple list:

> users.map(name)
[
  0: "root",

  ...

]

This makes many queries and assertions easier:

users.map(name).contains( "anya" )

Maps

Maps are key-value structures where keys are strings and values can be any type. You can access individual entries with [] or retrieve all keys and values.

Here are some basic examples:

m = {"a": 1, "b": 2}

> m.b
# 2

> m.keys
# ["a", "b"]

> m.values
# [1, 2]

Here's how you'd use bracket notation with a real resource:

> os.env["SHELL"]
"/usr/bin/zsh"

Map assertions

The available assertions for maps are:

{'a': 1, 'b': 2}.contains( key == 'b' )
{'a': 1, 'b': 2}.all( value > 0 )
{'a': 1, 'b': 2}.one( value != 1 )
{'a': 1, 'b': 2}.none( key == /d-f/ )

Dicts

Dicts handle data where value types aren't known ahead of time, such as parsed JSON. Unlike maps (which have a fixed value type), dicts can contain mixed types in the same structure:

> parse.json("my.json")
parse.json.params: {
  1: 1.000000
  1.0: 1.000000
  _: null
  date: "2016-01-28T23:02:24Z"
  dict: {
	ee: 3.000000
	ej: 4.000000
	ek: 5.000000
  }

  ...

As shown, a single dict can hold strings, numbers, nulls, and nested dicts.

You can access keys, values, and nested entries the same way as with maps:

> parse.json("my.json").params.keys
parse.json.params.keys: [
  0: "int-array"
  1: "f"
  2: "string-array"
  3: "hello"


> parse.json("my.json").params.values
parse.json.params.values: [
  0: null
  1: true
  2: 1.000000
  3: "hello"

> parse.json("my.json").params["f"][0]
parse.json.params[f][0]: {
  ff: 3.000000
}

> parse.json("my.json").
    params["f"].
    all( _.keys.contains("ff") )
[ok] value: true

recurse helper for dicts

The recurse helper makes it easy to extract data from a dict structure made up of mixed value types.

For example, suppose you need to retrieve all users from this JSON data structure:

{
  "users": [{ "name": "bob" }],
  "owners": {
    "admins": [{ "name": "joy", "isOwner": true }]
  }
}

Because users appear at different nesting levels and under different keys, finding them all requires knowing the exact structure. recurse solves this by searching the entire tree for entries that match a condition:

jdata.recurse( name != empty )
[
  0: {
    name: "bob"
  }
  1: {
    isOwner: true
    name: "joy"
  }
]

You can then map the user names:

jdata.recurse( name != empty ).map(name)
[
  0: "bob"
  1: "joy"
]

Nested data

JSON, Terraform, and Kubernetes artifacts often have deeply nested structures. MQL supports simple dot-notation accessors to reach into them:

tfblock {
  attributes.account_id.value
}

Helpers for data type conversions

Helpers let you convert data to the type you need:

> int(1.23)
1

> bool(1)
true

> float(12)
12

> string(1.89)
"1.89"

> regex("w.r.d") == "world 🌎"
/w.r.d/

Error handling

When a value can't be accessed, MQL returns an error with a description of what went wrong:

> file("/etc/shadow").content
[failed] file.content
  error: open /etc/shadow: permission denied

Null chaining

When a field in a chain returns null, MQL propagates the null through the rest of the chain instead of throwing an error:

> sshd.config.params["NONE"].downcase == null
[ok] value: _

Concurrency

MQL runs operations concurrently by default. When a query involves multiple independent data sources, MQL fetches them in parallel automatically.

For example:

hosts = [
  tls("mondoo.com"),
  tls("mondoo.io"),
  ...
]

hosts.all(
  ciphers
    .none( /cbc/i )
)

This query checks TLS ciphers across all the listed hosts. Rather than connecting to each host one at a time, MQL sends all the TLS requests in parallel and aggregates the results.

This works the same regardless of whether the data comes from an API, a file, a system command, or any other source. You don't need to configure anything; concurrency is always automatic.

The interactive shell

Both cnspec and cnquery provide a built-in interactive shell that acts like an IDE for MQL. This is one of the most powerful tools for:

  • Learning MQL: experiment with selectors, built-ins, regex, and comparisons live.
  • Testing policies: copy queries from a policy file and run them interactively.
  • Investigating live systems: connect to a cloud account, Kubernetes cluster, or host to query live state.
  • Incident response: during a security event, you can ask questions of your infrastructure in real time, without writing code or waiting for dashboards to refresh.

Think of the shell as a REPL (Read-Eval-Print Loop) for your infrastructure. You type MQL queries, it evaluates them against your connected system, and prints results immediately.


How to launch the interactive shell

Pick a target and start a shell:

# Local OS
cnspec shell local

# AWS account (uses your AWS credentials)
cnspec shell aws

# GCP project (replace with your project ID)
cnspec shell gcp project <project-id>

# Azure -- lists your subscriptions, select one by number
cnspec shell azure

# Kubernetes -- uses current KUBECONFIG and lists clusters/namespaces to select
cnspec shell k8s

When you connect, you'll see a banner confirming the provider and the asset you are connected to.


Built-in help

The shell has a built-in help command. Use it to explore what resources are available and what fields they expose.

Provider-level help:

cnspec> help aws
cnspec> help gcp
cnspec> help k8s

Resource-level help:

cnspec> help aws.ec2.instance
cnspec> help gcp.project
cnspec> help k8s.deployment

Each help command shows fields, types, and nested resources you can query.

Examples of shell in action

The following examples show how you might investigate a live security incident.

Which AWS EC2 instances have a public IP?

aws.ec2.instances.where(publicIp != empty) { instanceId region state tags publicIp }

Which GCP compute instances are running with external NAT?

gcp.compute.instances.where(networkInterfaces.where(_['accessConfigs'].where(_['name'] == "External NAT")))

Which Kubernetes pods are not in Running state?

k8s.pods.where(manifest.status.phase != "Running") { name namespace }

Which Linux services are running?

services.where(running == true) { name enabled type }

You can run these queries live during incident response to quickly answer pressing questions without waiting for dashboards to refresh.

Why the shell matters

The interactive shell is more than a convenience:

  • It is the fastest way to learn MQL by trial and error.
  • It provides deep visibility into resources through the help command.
  • It allows ad-hoc investigation without pre-built dashboards.
  • It bridges the gap between writing policies and running live checks.

MQL built-in functions

Built-in functions let you filter, transform, and make assertions across collections of data. They are the building blocks for turning raw data into meaningful answers.

  • .where() narrows down what you're looking at.
  • .map() transforms the results.
  • .all(), .any(), .none(), .one() let you assert truth over collections.
  • .list, .length, and .containsOnly help you structure and compare results.

For detailed documentation with examples of every built-in function, see the MQL Functions Reference.

Quick reference for MQL built-ins

Built-inInput typesOutputTypical use
addressstring (CIDR)stringReturn the network address of a CIDR
all(cond)arrays, iterablesbooleanAssert all elements match (universal requirement)
any(cond)arrays, iterablesbooleanAssert at least one element matches
camelcasestringstringConvert to camelCase
cidrstring (CIDR)stringReturn the CIDR block
contains(val)arrays, stringsbooleanCheck membership or substring presence
containsAll([])arrays, stringsbooleanAssert all listed values are present
containsNone([])arrays, stringsbooleanAssert none of the listed values are present
containsOnly([])arrays of scalarsbooleanAssert only allowed values are present
datetime, epochstringFormat or display a date
daysintegerdurationConvert number into days duration
downcasestringstringConvert to lowercase
duplicatesarraysarrayReturn duplicate elements
durationinteger, epoch differencedurationRepresent elapsed time
epochtimeintReturn Unix epoch
find(params)provider resourcesarrayDiscover resources in a scope
firstarrayselementReturn the first element
flatnested arraysarrayFlatten nested arrays
hoursintegerdurationConvert number into hours duration
in(list)scalar, listbooleanTest membership in a list
inRange(val,min,max)numbersbooleanCheck if value is within range
isUnspecifiedstring (IP or CIDR)booleanTest if value is unspecified (e.g., 0.0.0.0)
keysmap/dictarrayReturn keys of a dictionary
lastarrayselementReturn the last element
lengtharrays, stringsintegerCount elements or characters
linesstringarraySplit into lines
map(expr)arrays, iterablesarrayTransform or project values
minutesintegerdurationConvert number into minutes duration
none(cond)arrays, iterablesbooleanAssert no elements match
notIn(list)scalar, listbooleanTest value is not in list
one(cond)arrays, iterablesbooleanAssert exactly one element matches
prefixstring (CIDR)stringReturn network prefix
prefixLengthstring (CIDR)integerReturn prefix length
recursenested resourcesarrayTraverse nested structures
samplearrayselementReturn a random element
secondsintegerdurationConvert number into seconds duration
splitstringarraySplit by delimiter
subnetstring (CIDR)stringReturn subnet portion
suffixstringstringReturn suffix (network utility)
trimstringstringRemove leading/trailing whitespace
uniquearraysarrayReturn only unique elements
unixepochtimeConvert epoch to time
upcasestringstringConvert to uppercase
valuesmap/dictarrayReturn values of a dictionary
versionstring (semver)versionParse and compare version strings
where(cond)arrays, iterablesarrayFilter a collection by condition

Additional resources

Explore more in the Mondoo docs:

PagePurpose
Policy Authoring GuideDescribes how to write Mondoo security policies
MQL ResourcesLists all of the information that MQL can retrieve from infrastructure assets and describes how to use them
Get Started with cnqueryDescribes how to use the cnquery shell for ad hoc MQL queries
Query Your InfrastructureDescribes how to write queries to execute from the command line or to use in automation
cnquery CLI commandsDetails all commands in the cnquery command line interface
Create Checks in cnspec ShellDescribes how to use the cnspec shell for ad hoc MQL assertions
cnspec CLI commandsDetails all commands in the cnspec command line interface

On this page