> For the complete documentation index, see [llms.txt](https://cortex-docs.paloaltonetworks.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://cortex-docs.paloaltonetworks.com/cortex-cloud-runtime-security/rules-and-policies/cloud-workload-policies-and-rules/cloud-workload-rules/create-a-new-custom-detection-rule/configure-agentless-disk-scan-rule.md).

# Configure Agentless disk scan rule

The **Agentless Disk Scan** evaluates files on a disk image using rules written in **Rego**. The scanner provides the content and metadata of each declared file through a defined input structure that your rules can evaluate.

### Rule structure

Agentless Disk Scan rules use the following structure:

* Declare the `panw.compliance` package.
* Import `rego.v1`.
* Define one or more `match` rules to specify the detection logic.
* Assign a human-readable finding description to `msg`.

```rego
package panw.compliance

import rego.v1

match contains {"msg": msg} if {
    # Your detection logic here
    msg := "Description of the finding"
}
```

### Input structure

For each file declared as a dependency, the scanner adds an entry to the top-level `files` object. The entry is keyed by the input identifier specified when the dependency is declared.

```
{
  "files": {
    "<your-input-id>": {
      "path": "/absolute/path/you/requested",
      "content": "<file contents as a string; omitted for directories>",
      "metadata": {
        "size": 123,
        "permissions": 420,
        "last_modified": 1737292449,
        "file_type": "file",
        "uid": 1000,
        "gid": 1000
      }
    }
  }
}
```

| **Field**                | **Description**                                                                                                                 |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------- |
| `files`                  | Top-level object containing the results for all declared file dependencies.                                                     |
| `<your-input-id>`        | The identifier specified when declaring the dependency. Use it to reference a specific file or iterate over all declared files. |
| `path`                   | Absolute path of the requested file or directory.                                                                               |
| `content`                | File contents as a string. Present for regular files and omitted for directories.                                               |
| `metadata.size`          | File size in bytes.                                                                                                             |
| `metadata.permissions`   | File mode represented as a decimal integer.                                                                                     |
| `metadata.last_modified` | Last modification time as a Unix timestamp in seconds.                                                                          |
| `metadata.file_type`     | Filesystem object type: `file` or `dir`.                                                                                        |
| `metadata.uid`           | User ID of the file owner.                                                                                                      |
| `metadata.gid`           | Group ID of the file owner.                                                                                                     |

### Working with file permissions

The `metadata.permissions` value is the file mode represented as a **decimal integer**, rather than the octal notation commonly used for file permissions. Convert the expected octal value to decimal before comparing it in a rule.

| **Octal mode** | **Decimal value** |
| -------------- | ----------------: |
| `0600`         |             `384` |
| `0640`         |             `416` |
| `0644`         |             `420` |
| `0755`         |             `493` |

To keep your rule readable, define the expected permission value as a named constant:

```rego
expected_perms := 384  # 0o600
```

The following examples show how the scanner input is evaluated by a rule and how a matching rule produces a finding.

### Examples

#### 1. Detect that a file exists

This example checks whether `/root/test.txt` exists.

**Input**

```
{
  "files": {
    "test_file": {
      "path": "/root/test.txt",
      "content": "",
      "metadata": {
        "size": 0,
        "permissions": 384,
        "last_modified": 1737292449,
        "file_type": "file",
        "uid": 0,
        "gid": 0
      }
    }
  }
}
```

**Rule**

```rego
package panw.compliance

import rego.v1

match contains {"msg": msg} if {
    some id
    f := input.files[id]
    f.path == "/root/test.txt"
    msg := sprintf("Violation: %v exists on the system", [f.path])
}
```

**Output**

```
{
  "match": [
    {
      "msg": "Violation: /root/test.txt exists on the system"
    }
  ]
}
```

#### 2. Check file permissions

This example checks whether `/etc/shadow` has permissions other than `0600`.

**Input**

```
{
  "files": {
    "shadow_file": {
      "path": "/etc/shadow",
      "content": "<file contents>",
      "metadata": {
        "size": 100,
        "permissions": 416,
        "last_modified": 1737292449,
        "file_type": "file",
        "uid": 0,
        "gid": 0
      }
    }
  }
}
```

Here, `416` is the decimal representation of `0640`.

**Rule**

```rego
package panw.compliance

import rego.v1

expected_perms := 384  # 0o600

match contains {"msg": msg} if {
    some id
    f := input.files[id]
    f.path == "/etc/shadow"
    f.metadata.file_type == "file"
    f.metadata.permissions != expected_perms
    msg := sprintf("/etc/shadow has permissions %v; expected 0600 (384)", [f.metadata.permissions])
}
```

**Output**

```json
{
  "match": [
    {
      "msg": "/etc/shadow has permissions 416; expected 0600 (384)"
    }
  ]
}
```

#### 3. Match file content

This example checks whether `/etc/ssh/sshd_config` contains a setting that allows root login.

**Input**

```
{
  "files": {
    "sshd_config": {
      "path": "/etc/ssh/sshd_config",
      "content": "Port 22\nPermitRootLogin yes\nPasswordAuthentication no\n",
      "metadata": {
        "size": 58,
        "permissions": 420,
        "last_modified": 1737292449,
        "file_type": "file",
        "uid": 0,
        "gid": 0
      }
    }
  }
}
```

**Rule**

```rego
package panw.compliance

import rego.v1

match contains {"msg": msg} if {
    some id
    f := input.files[id]
    f.path == "/etc/ssh/sshd_config"
    contains(f.content, "PermitRootLogin yes")
    msg := "/etc/ssh/sshd_config allows root login"
}
```

**Output**

```
{
  "match": [
    {
      "msg": "/etc/ssh/sshd_config allows root login"
    }
  ]
}
```

#### 4. Detect a missing hardening setting

This example produces a finding when `/etc/ssh/sshd_config` does not explicitly disable root login.

**Input**

```
{
  "files": {
    "sshd_config": {
      "path": "/etc/ssh/sshd_config",
      "content": "Port 22\nPasswordAuthentication no\n",
      "metadata": {
        "size": 38,
        "permissions": 420,
        "last_modified": 1737292449,
        "file_type": "file",
        "uid": 0,
        "gid": 0
      }
    }
  }
}
```

**Rule**

```rego
package panw.compliance

import rego.v1

match contains {"msg": msg} if {
    some id
    f := input.files[id]
    f.path == "/etc/ssh/sshd_config"
    not contains(f.content, "PermitRootLogin no")
    msg := "/etc/ssh/sshd_config does not explicitly disable root login"
}
```

**Output**

```
{
  "match": [
    {
      "msg": "/etc/ssh/sshd_config does not explicitly disable root login"
    }
  ]
}
```

#### 5. Reuse logic with a helper function

Use a helper function when the same detection logic is needed in multiple rules.

This example defines a helper function that checks whether a specified file exists.

**Input**

```
{
  "files": {
    "aws_secrets": {
      "path": "/opt/secrets.aws",
      "content": "<file contents>",
      "metadata": {
        "size": 75,
        "permissions": 384,
        "last_modified": 1737292449,
        "file_type": "file",
        "uid": 1000,
        "gid": 1000
      }
    }
  }
}
```

**Rule**

```rego
package panw.compliance

import rego.v1

check_file(p) if {
    some id
    f := input.files[id]
    f.path == p
}

match contains {"msg": msg} if {
    check_file("/opt/secrets.aws")
    msg := "Violation: /opt/secrets.aws exists on the system"
}
```

**Output**

```
{
  "match": [
    {
      "msg": "Violation: /opt/secrets.aws exists on the system"
    }
  ]
}
```

#### 6. Require multiple conditions (AND)

Place multiple conditions in the same `match` rule when **all conditions must be satisfied** to produce a finding.

This example produces a finding only when both secret files exist and are non-empty.

**Input**

```
{
  "files": {
    "aws_secrets": {
      "path": "/opt/secrets.aws",
      "content": "AWS credentials",
      "metadata": {
        "size": 16,
        "permissions": 384,
        "last_modified": 1737292449,
        "file_type": "file",
        "uid": 1000,
        "gid": 1000
      }
    },
    "gcp_secrets": {
      "path": "/opt/secrets.gcp",
      "content": "GCP credentials",
      "metadata": {
        "size": 16,
        "permissions": 384,
        "last_modified": 1737292449,
        "file_type": "file",
        "uid": 1000,
        "gid": 1000
      }
    }
  }
}
```

**Rule**

```rego
package panw.compliance

import rego.v1

secrets_file_found(p) if {
    some id
    f := input.files[id]
    f.path == p
    f.metadata.file_type == "file"
    f.metadata.size > 0
}

match contains {"msg": msg} if {
    secrets_file_found("/opt/secrets.aws")
    secrets_file_found("/opt/secrets.gcp")
    msg := "Both the AWS and GCP secret files exist on the system"
}
```

**Output**

```
{
  "match": [
    {
      "msg": "Both the AWS and GCP secret files exist on the system"
    }
  ]
}
```

#### 7. Match any of several conditions (OR)

Define a separate `match` rule for each condition when **any one of several conditions** should produce a finding.

This example produces a finding when either secret file exists and is larger than 100 bytes.

**Input**

```
{
  "files": {
    "aws_secrets": {
      "path": "/opt/secrets.aws",
      "content": "<file contents>",
      "metadata": {
        "size": 150,
        "permissions": 384,
        "last_modified": 1737292449,
        "file_type": "file",
        "uid": 1000,
        "gid": 1000
      }
    },
    "gcp_secrets": {
      "path": "/opt/secrets.gcp",
      "content": "<file contents>",
      "metadata": {
        "size": 120,
        "permissions": 384,
        "last_modified": 1737292449,
        "file_type": "file",
        "uid": 1000,
        "gid": 1000
      }
    }
  }
}
```

**Rule**

```rego
package panw.compliance

import rego.v1

secrets_file_found(p) if {
    some id
    f := input.files[id]
    f.path == p
    f.metadata.file_type == "file"
    f.metadata.size > 100
}

match contains {"msg": msg} if {
    secrets_file_found("/opt/secrets.aws")
    msg := "The AWS secret file (larger than 100 bytes) exists on the system"
}

match contains {"msg": msg} if {
    secrets_file_found("/opt/secrets.gcp")
    msg := "The GCP secret file (larger than 100 bytes) exists on the system"
}
```

**Output**

```
{
  "match": [
    {
      "msg": "The AWS secret file (larger than 100 bytes) exists on the system"
    },
    {
      "msg": "The GCP secret file (larger than 100 bytes) exists on the system"
    }
  ]
}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://cortex-docs.paloaltonetworks.com/cortex-cloud-runtime-security/rules-and-policies/cloud-workload-policies-and-rules/cloud-workload-rules/create-a-new-custom-detection-rule/configure-agentless-disk-scan-rule.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
