# Introduction

... or what secator is all about.

`secator` is a task and workflow runner used for security assessments. It supports dozens of well-known security tools and is designed to improve productivity for pentesters and security researchers.

***

## Quick demo

<figure><img src="https://3064929067-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F787L9q3BUjAZ71WVY90V%2Fuploads%2FpsAxpJFKFINjfQsF6FgQ%2Fdemo.gif?alt=media&amp;token=cfc2381f-eafb-44a3-a688-182b998e6037" alt=""><figcaption><p>Example of running secator tasks and workflows</p></figcaption></figure>

***

## Features

* [Philosophy & design](/in-depth/philosophy-and-design#curated-list-of-tools)
* [Philosophy & design](/in-depth/philosophy-and-design#unified-input-options)
* [Philosophy & design](/in-depth/philosophy-and-design#unified-output-schema)
* [Philosophy & design](/in-depth/philosophy-and-design#cli-and-library-usage)
* [Philosophy & design](/in-depth/philosophy-and-design#distributed-options)
* [Philosophy & design](/in-depth/philosophy-and-design#from-simple-tasks-to-complex-workflows)
* [Philosophy & design](/in-depth/philosophy-and-design#customizable)

***

## Supported tools

`secator` integrates the following tools:

| Name                                                           | Description                                                                                              | Category              |
| -------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | --------------------- |
| [`ai`](/in-depth/ai-task)                                      | AI-powered penetration testing assistant (scan, chat or exploit mode).                                   | `ai/analysis/pentest` |
| [arjun](https://github.com/s0md3v/Arjun)                       | HTTP Parameter Discovery Suite.                                                                          | `url/fuzz/params`     |
| `arp`                                                          | Display the system ARP cache.                                                                            | `ip/recon`            |
| [arpscan](https://github.com/royhills/arp-scan)                | Scan a CIDR range for alive hosts using ARP.                                                             | `ip/recon`            |
| [bbot](https://github.com/blacklanternsecurity/bbot)           | Multipurpose scanner.                                                                                    | `vuln/scan`           |
| [bup](https://github.com/laluka/bypass-url-parser)             | 40X bypasser.                                                                                            | `url/bypass`          |
| [cariddi](https://github.com/edoardottt/cariddi)               | Crawl endpoints, secrets, api keys, extensions, tokens...                                                | `url/crawl`           |
| `command`                                                      | Run an arbitrary shell command verbatim.                                                                 | `misc`                |
| [dalfox](https://github.com/hahwul/dalfox)                     | Powerful open source XSS scanning tool.                                                                  | `url/fuzz`            |
| [dnsx](https://github.com/projectdiscovery/dnsx)               | dnsx is a fast and multi-purpose DNS toolkit designed for running various retryabledns library.          | `dns/fuzz`            |
| [feroxbuster](https://github.com/epi052/feroxbuster)           | Simple, fast, recursive content discovery tool written in Rust                                           | `url/fuzz`            |
| [ffuf](https://github.com/ffuf/ffuf)                           | Fast web fuzzer written in Go.                                                                           | `url/fuzz`            |
| [fping](https://github.com/schweikert/fping)                   | Send ICMP echo probes to network hosts, similar to ping, but much better.                                | `ip/recon`            |
| [gau](https://github.com/lc/gau)                               | Fetch known URLs from AlienVault's Open Threat Exchange, the Wayback Machine, Common Crawl, and URLScan. | `url/crawl/passive`   |
| [getasn](https://github.com/freelabz/getasn)                   | Get ASN information from IP address.                                                                     | `ip/probe`            |
| [gf](https://github.com/tomnomnom/gf)                          | Wrapper around grep, to help you grep for things.                                                        | `pattern/scan`        |
| [gitleaks](https://github.com/gitleaks/gitleaks)               | Tool for detecting secrets like passwords, API keys, and tokens in git repos, files, and stdin.          | `secret/scan`         |
| [gospider](https://github.com/jaeles-project/gospider)         | Fast web spider written in Go.                                                                           | `url/crawl`           |
| [grype](https://github.com/anchore/grype)                      | Vulnerability scanner for container images and filesystems.                                              | `vuln/scan`           |
| [h8mail](https://github.com/khast3x/h8mail)                    | Email information and password lookup tool.                                                              | `user/recon/email`    |
| [httpx](https://github.com/projectdiscovery/httpx)             | Fast and multi-purpose HTTP toolkit.                                                                     | `url/probe`           |
| [jswhois](https://github.com/freelabz/jswhois)                 | WHOIS in JSON format                                                                                     | `domain/info`         |
| [katana](https://github.com/projectdiscovery/katana)           | Next-generation crawling and spidering framework.                                                        | `url/crawl`           |
| [maigret](https://github.com/soxoj/maigret)                    | Collect a dossier on a person by username.                                                               | `user/recon/username` |
| [mapcidr](https://github.com/projectdiscovery/mapcidr)         | Utility program to perform multiple operations for a given subnet/cidr ranges.                           | `ip/recon`            |
| [msfconsole](https://docs.rapid7.com/metasploit/msf-overview/) | CLI to access and work with the Metasploit Framework.                                                    | `exploit/attack`      |
| [naabu](https://github.com/projectdiscovery/naabu)             | Port scanning tool written in Go.                                                                        | `port/scan`           |
| `netdetect`                                                    | Detect local network CIDR ranges.                                                                        | `network/recon`       |
| [nmap](https://github.com/nmap/nmap)                           | Network Mapper is a free and open source utility for network discovery and security auditing.            | `port/scan`           |
| [nuclei](https://github.com/projectdiscovery/nuclei)           | Fast and customisable vulnerability scanner based on simple YAML based DSL.                              | `vuln/scan`           |
| [ph](https://github.com/freelabz/ph)                           | Pattern matcher that tags interesting patterns found in URLs or text.                                    | `pattern/scan`        |
| `prompt`                                                       | Prompt the user.                                                                                         | `network/recon`       |
| [search\_vulns](https://github.com/ra1nb0rn/search_vulns)      | Search for known vulnerabilities in software by product name or CPE.                                     | `vuln/recon`          |
| [searchsploit](https://gitlab.com/exploit-database/exploitdb)  | Exploit searcher based on ExploitDB.                                                                     | `exploit/recon`       |
| [sshaudit](https://github.com/jtesta/ssh-audit)                | SSH server & client security auditing (banner, key exchange, encryption, mac, compression, etc).         | `ssh/audit/security`  |
| [subfinder](https://github.com/projectdiscovery/subfinder)     | Fast passive subdomain enumeration tool.                                                                 | `dns/recon/passive`   |
| [testssl](https://github.com/testssl/testssl.sh)               | SSL/TLS security scanner, including ciphers, protocols and cryptographic flaws.                          | `dns/recon/tls`       |
| [trivy](https://github.com/aquasecurity/trivy)                 | Comprehensive and versatile security scanner.                                                            | `vuln/scan`           |
| [trufflehog](https://github.com/trufflesecurity/trufflehog)    | Tool for finding secrets in git repositories and filesystems using TruffleHog.                           | `secret/scan`         |
| [urlfinder](https://github.com/projectdiscovery/urlfinder)     | Find URLs in text.                                                                                       | `url/crawl/passive`   |
| `urlparser`                                                    | Extract query string parameters from URLs                                                                | `url/params`          |
| [wafw00f](https://github.com/EnableSecurity/wafw00f)           | Web Application Firewall Fingerprinting tool.                                                            | `waf/scan`            |
| [whois](https://github.com/likexian/whois)                     | The whois tool from likexian retrieves domain registration information in JSON format.                   | `misc`                |
| [whoisdomain](https://github.com/mboot-github/WhoisDomain)     | The whois tool retrieves registration information about domain names and IP addresses.                   | `misc`                |
| [wpprobe](https://github.com/Chocapikk/wpprobe)                | Fast wordpress plugin enumeration tool.                                                                  | `vuln/scan/wordpress` |
| [wpscan](https://github.com/wpscanteam/wpscan)                 | Wordpress security scanner.                                                                              | `vuln/scan/wordpress` |
| [x8](https://github.com/freelabz/x8)                           | Hidden parameters discovery suite written in Rust.                                                       | `url/fuzz/params`     |
| [xurlfind3r](https://github.com/hueristiq/xurlfind3r)          | Discover URLs for a given domain in a simple, passive and efficient way                                  | `url/crawl/passive`   |

{% hint style="info" %}
`ai`, `arp`, `command`, `netdetect`, `prompt` and `urlparser` are built-in tasks that do not wrap an external tool.
{% endhint %}

{% hint style="info" %}
Feel free to request new tools integrations by [opening an issue](https://github.com/freelabz/secator/issues/new) on the repo, but please check that the tool complies with our selection criterias before doing so (read [Philosophy & design](/in-depth/philosophy-and-design#curated-list-of-tools)). If it doesn't but you still want to integrate it into `secator`, you can plug it in (read [Writing tasks](/for-developers/writing-tasks)).
{% endhint %}

***


# Installation

... or how to install secator and it's dependencies on different platforms.

***

## Installing secator

{% tabs %}
{% tab title="Pipx" %}

```bash
pipx install secator
```

{% endtab %}

{% tab title="Pip" %}

```bash
pip install secator
```

{% endtab %}

{% tab title="Bash" %}

```bash
bash -c "$(curl -fsSL https://raw.githubusercontent.com/freelabz/secator/main/scripts/install_universal.sh)"
```

The script accepts optional flags (`--version <version>`, `--addons <list>`, `--tools [<list>]`, `--templates <path>`), for instance:

```bash
curl -fsSL https://raw.githubusercontent.com/freelabz/secator/main/scripts/install_universal.sh -o install_secator.sh
bash install_secator.sh --addons worker,mongodb --tools httpx,nuclei
```

{% endtab %}

{% tab title="Docker" %}

```bash
docker run -it --rm --net=host -v ~/.secator:/root/.secator freelabz/secator --help
```

{% hint style="info" %}
The volume mount `-v` is necessary to save all `secator` reports to your host machine, and`--net=host` is recommended to grant full access to the host network.
{% endhint %}

You can alias this command to run it easier:

```
alias secator="docker run -it --rm --net=host -v ~/.secator:/root/.secator freelabz/secator"
```

Now you can run `secator` like if it was installed on baremetal:

```
secator --help
```

{% endtab %}

{% tab title="Docker Compose" %}

<pre class="language-bash"><code class="lang-bash">git clone https://github.com/freelabz/secator
cd secator
<strong>docker compose up -d
</strong><strong>docker compose exec secator secator --help
</strong></code></pre>

{% endtab %}
{% endtabs %}

{% hint style="success" %}
If you chose the Bash, Docker, or Docker Compose installation methods, you can jump straight to [CLI Usage](/getting-started/cli-usage).
{% endhint %}

***

## Installing languages (optional)

`secator` uses external tools, so you might need to install languages used by those tools assuming they are not already installed on your system.

We provide a subcommand to install required languages if you don't manage them externally:

<pre class="language-bash"><code class="lang-bash"><strong>secator install langs go   # install Go
</strong>secator install langs ruby # install Ruby
</code></pre>

***

## Installing tools (optional)

`secator` can install tools automatically at runtime (provided `security.auto_install_commands` is enabled, which is the default), but you can also do it manually.

We provide a subcommand to install or update each supported tool which should work on all systems supporting `apt`:

```bash
secator install tools httpx  # install httpx
secator install tools        # install all supported tools
```

***

## Installing addons (optional)

`secator` comes installed with the minimum amount of dependencies.

We provide a subcommand to install additional addons which are required for various features:

{% tabs %}
{% tab title="worker" %}
Add support for Celery (see [Distributed runs with Celery](/in-depth/distributed-runs-with-celery)).

```sh
secator install addons worker
```

{% endtab %}

{% tab title="gdrive" %}
Add support for the Google Drive exporter (see [Exporters](/in-depth/concepts/exporters)).

```sh
secator install addons gdrive
```

{% endtab %}

{% tab title="gcs" %}
Add support for the Google Cloud Storage driver (see [Drivers](/in-depth/concepts/drivers#gcs-driver)).

```sh
secator install addons gcs
```

{% endtab %}

{% tab title="mongodb" %}
Add support for MongoDB driver (see [Drivers](/in-depth/concepts/drivers#mongodb-driver)).

```sh
secator install addons mongodb
```

{% endtab %}

{% tab title="redis" %}
Add support for Celery Redis broker / backend.

```sh
secator install addons redis
```

{% endtab %}

{% tab title="vulners" %}
Add support for the Vulners CVE provider (set `addons.vulners.api_key` and `providers.defaults.cve` to `vulners`).

```sh
secator install addons vulners
```

{% endtab %}

{% tab title="ai" %}
Add support for the `ai` task (see [AI task](/in-depth/ai-task)).

```sh
secator install addons ai
```

{% endtab %}

{% tab title="dev" %}
Add development tools like `coverage` and `flake8` required for running tests.

```sh
secator install addons dev
```

{% endtab %}

{% tab title="trace" %}
Add tracing tools like `memray` and `pyinstrument` required for tracing functions.

```sh
secator install addons trace
```

{% endtab %}

{% tab title="build" %}
Add the packaging tools needed by `secator u build` and `secator u publish`.

```sh
secator install addons build
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
The SQLite driver does not need an addon: it uses Python's built-in `sqlite3` module.
{% endhint %}

***

## Updating secator

To update `secator` to the latest version:

```bash
secator update      # update secator
secator update -a   # also update addons, tools, ...
```

***

## Shell completion (optional)

`secator` supports shell completion for `bash`, `zsh` and `fish` (task, workflow, scan and profile names, and options such as `--workspace`, `--driver` or `--output`):

```bash
secator util completion --shell zsh --install  # install completion in ~/.zshrc
source ~/.zshrc
```

Run `secator util completion --shell <SHELL>` without `--install` to print the completion script instead.

***

## Checking installation health

To figure out which languages or tools are installed on your system (along with their version):

```bash
secator health            # show installed languages and tools with their version
secator health --strict   # exit with an error if a tool is missing (useful in CI)
secator health --json     # JSON lines output
```

<div align="left"><figure><img src="https://3064929067-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F787L9q3BUjAZ71WVY90V%2Fuploads%2F5QyuAxGetZsdyRoLSbYd%2F2025-04-29%2015_55_22-Debian%2010.x%2064-bit%20-%20VMware%20Workstation.png?alt=media&amp;token=522dcf70-950c-4bdc-827e-af4311342d54" alt=""><figcaption><p>Secator Health CLI Output</p></figcaption></figure></div>

***


# CLI Usage

... or how you can use secator as your pentesting swiss-knife.

`secator` is first and foremost a command-line interface (CLI). This page describes how to use it in-depth.

***

## Usage

```bash
secator --help # General help
secator x      # List available tasks
secator w      # List available workflows
secator s      # List available scans
secator q      # Query results
secator r      # Manage reports
secator ws     # Manage workspaces
secator p      # Manage profiles
secator c      # View or edit config
secator u      # List available utilities
secator cs     # Show an interactive cheatsheet
```

***

## Running tasks

You can run any of the supported tasks out-of-the box using the `secator x` (execute) subcommand:

{% tabs %}
{% tab title="subfinder" %}
Find subdomains of a domain using offline sources with `subfinder`:

```bash
secator x subfinder wikipedia.org
```

{% endtab %}

{% tab title="httpx" %}
Find information about an URL with `httpx`:

```bash
secator x httpx wikipedia.org
```

{% endtab %}

{% tab title="ffuf" %}
Fuzz URLs with `ffuf` with max 100 requests / second and matching select HTTP codes:

```bash
secator x ffuf http://testphp.vulnweb.com/FUZZ -rl 100 -mc 200,201,300,500
```

{% endtab %}

{% tab title="nmap" %}
Find open ports and associated vulnerabilities with `nmap` using proxychains as a proxy:

```bash
secator x nmap myhost.com -p 443,80,8080,8081,21 -proxy proxychains
```

{% endtab %}

{% tab title="maigret" %}
Find user accounts with `maigret`:

```bash
secator x maigret elonmusk
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
Use **`secator x <NAME> --help`** to list options for a specific task.
{% endhint %}

***

## Running workflows

A workflow is a set of pre-defined tasks.

You can run some pre-written workflows using the `secator w` (workflow) subcommand:

{% tabs %}
{% tab title="Host recon" %}
To perform a basic host recon (open ports, network + HTTP vulnerabilities):

```bash
secator w host_recon 192.168.1.18
```

{% endtab %}

{% tab title="Subdomain recon" %}
To perform a basic subdomain discovery (subdomain + root URLs):

```bash
secator w subdomain_recon mydomain.com
```

{% endtab %}

{% tab title="URL crawl" %}
To perform URL crawling:

```bash
secator w url_crawl https://mydomain.com/start/crawling/from/here/
```

{% endtab %}

{% tab title="URL fuzz" %}
To perform URL fuzzing:

```bash
secator w url_fuzz https://mydomain.com/start/fuzzing/from/here/
```

{% endtab %}

{% tab title="Code scan" %}
To perform code vulnerability scan:

```bash
secator w code_scan /path/to/code/repo
```

{% endtab %}

{% tab title="User hunt" %}
To find user accounts for a username:

```bash
secator w user_hunt elonmusk
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
Use **`secator w <NAME> --help`** to list options for a specific workflow.
{% endhint %}

***

## Running scans

A scan is a set of workflows that run one after the other.

You can run some pre-written scans using the `secator s` subcommand:

{% tabs %}
{% tab title="Domain scan" %}

```
secator s domain example.com
```

{% endtab %}

{% tab title="Subdomain scan" %}

```
secator s subdomain sub.example.com
```

{% endtab %}

{% tab title="Host scan" %}

```
secator s host example.com
```

{% endtab %}

{% tab title="Network scan" %}

```
secator s network 192.168.1.0/24
```

{% endtab %}

{% tab title="URL Scan" %}

```
secator s url http://testphp.vulnweb.com
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
Use **`secator s <NAME> --help`** to list a options for a specific scan.
{% endhint %}

***

## Querying results

Every run is saved as a report. You can list reports, show their results, and query findings across all reports of a workspace:

```bash
secator r list                                     # list reports
secator r show tasks/3 -o csv                      # show (and export) a report's results
secator q "vulnerability.severity == 'critical'"   # query findings across reports
secator ws summary                                 # summarize the current workspace
```

{% hint style="info" %}
Learn more about [Querying results & reports](/getting-started/querying-results) and [Workspaces](/in-depth/concepts/workspaces).
{% endhint %}

***

## Running utils

`secator` provides a number of utilities that can be useful when doing pentesting.

### **Proxy**

You can get a random proxy:

```bash
secator u proxy                  # print a random proxy
secator u proxy -n 5 --timeout 1 # print 5 proxies with 1s max timeout
```

### **Reverse shells**

You can spawn reverse shells in any language, and optional netcat listener:

```bash
secator u revshell                                     # list all reverse shells
secator u revshell bash                                # show a Bash reverse shell
secator u revshell javascript -h <LHOST> -p <LPORT>    # show a Javascript reverse shell to connect to LHOST / LPORT
secator u revshell javascript -h <LHOST> -p <LPORT> -l # ... also spawn a netcat listener
```

### Serve

You can run an HTTP server to serve payloads:

```sh
secator u serve
```

### **Recording**

You can record pentesting sessions as a GIF:

```bash
secator u record -i <RECORD_NAME>                # record an interactive session
secator u record --script test.sh <RECORD_NAME>  # put your commands in a script and record the execution
```

### Shell completion

You can show or install shell completion for `bash`, `zsh` or `fish`:

```bash
secator u completion --shell bash --install
```

***

## Configuring secator

To configure `secator`, use the following commands:

```bash
secator c get                                     # get full config (with defaults)
secator c get --user                              # get user config
secator c get wordlists.defaults.http             # get default wordlist path
secator c set wordlists.defaults.http rockyou.txt # set default wordlist 
secator c unset wordlists.defaults.http           # reset key to default
secator c edit                                    # edit user config yaml
secator c default                                 # get default config
```

To see all available configuration options, read the [Configuration reference](/getting-started/configuration/configuration-reference) or run `secator c default`.

***

## Aliases

`secator` ships short shell aliases (`x`, `w`, `s`, ...) so you can omit the `secator` prefix:

```bash
secator alias list      # list aliases
secator alias enable    # write the aliases to ~/.secator/.aliases
source ~/.secator/.aliases
secator alias disable   # disable aliases
```

***

## Running a worker \[optional]

You can enable distributed runs by starting `secator` workers. All tasks / workflows / scans will be sent to the workers for execution.

You can run a worker using the file system as a broker and result backend:

```bash
secator install addons worker
secator worker
```

{% hint style="info" %}
Learn more about [Distributed runs with Celery](/in-depth/distributed-runs-with-celery)
{% endhint %}

***


# Library usage

... or how you can use secator as a foundation to build powerful security software.

`secator` can also be used as a Python library.

{% hint style="info" %}
We recommend using `secator` as a library when building complex systems around `secator` to overcome CLI limitations.
{% endhint %}

***

## **Running tasks, workflows, and scans**

You can run any task supported by `secator` by simply importing it by name from `secator.tasks`.

You can run any workflow or scan by importing it from `secator.workflows` or `secator.scans`.

```python
from secator.template import TemplateLoader
from secator.runners import Workflow
from secator.tasks import subfinder, httpx, naabu
from secator.workflows import host_recon
from secator.scans import host

# Run simple tasks, chain them together
target = 'wikipedia.org'
subdomains = subfinder(target).run()
hosts = list(set(_.host for _ in subdomains if _._type == 'subdomain'))
ports_open = naabu(hosts).run()
to_probe = list(set(f'{_.host}:{_.port}' for _ in ports_open if _._type == 'port'))
alive_urls = httpx(to_probe).run()

# ... or run a workflow
results = host_recon(target).run()

# ... or run a scan
results = host(target).run()

# ... or run any custom template by loading it dynamically
config = TemplateLoader('/path/to/my/workflow.yaml')
results = Workflow(config, target).run()
```

***

## Consuming results live

All runners yield results in real-time, which means you can run them as generators to consume their results:

For instance, you can consume results lazily using threads or a Celery task:

{% tabs %}
{% tab title="Using threads" %}

```python
from threading import Thread
from secator.tasks import feroxbuster
from secator.workflows import url_crawl
from secator.output_types import Url, Tag
from .models import Urls, Tags

def process_url(url):
    Urls.objects.create(**url.toDict())
    print(f'Saved {url.url} [{url.status_code}] to database')
    
def process_tag(tag):
    Tags.objects.create(**tag.toDict())
    print(f'Found tag {tag.name} for target {tag.match}')

# Set the initial host
host = 'http://testphp.vulnweb.com'

# Use a task as a generator
for url in feroxbuster(host, rate_limit=100):
    if isinstance(url, Url):
        Thread(target=process_url, args=(url,)).start()

# Use a workflow as a generator
threads = []
for result in url_crawl(host, rate_limit=100):
  if isinstance(result, Url):
    thread = Thread(target=process_url, args=(result,))
  elif isinstance(result, Tag):
    thread = Thread(target=process_tag, args=(result,))
  threads.append(thread)
  thread.start()
  
for thread in threads:
  thread.join()
```

{% endtab %}

{% tab title="Using a Celery task" %}

```python
from celery import Celery
from secator.tasks import feroxbuster
from secator.output_types import Url, Tag
from .models import Urls, Tags
from secator.workflows import url_crawl

app = Celery(__name__)

@app.task
def process_url(url):
    Urls.objects.create(**url)
    print(f'Saved {url["url"]} [{url["status_code"]}] to database')

@app.task
def process_tag(tag):
    Tags.objects.create(**tag)
    print(f'Found tag {tag["name"]} for target {tag["match"]}')

# Set the initial host
host = 'http://testphp.vulnweb.com'

# Use a task as a generator
for url in feroxbuster(host, rate_limit=100):
    if isinstance(url, Url):
        process_url.delay(url.toDict())

# Use a workflow as a generator
for result in url_crawl(host, rate_limit=100):
  if isinstance(result, Url):
    process_url.delay(result.toDict())
  elif isinstance(result, Tag):
    process_tag.delay(result.toDict())
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
All tasks support being run like generators, but some of them have to wait for the command to finish before outputting results (e.g: `nmap`).
{% endhint %}

***

## **Overriding global options**

Options specified with the name of the command name prefixed will override global options for that specific command.

For instance, if you want a global rate limit of `1000` (reqs/s), but for ffuf you want it to be `100` you can do so:

```python
from secator.tasks import ffuf, gau, gospider, katana
host = 'wikipedia.org'
options = {
    'rate_limit': 1000, # reqs/s
    'ffuf.rate_limit': 100,
    'katana.rate_limit': 30
}
for tool in [ffuf, gau, gospider, katana]:
    tool(host, **options)
```

{% hint style="info" %}
In the example above:

* `gau`, and `gospider` will have a rate limit of `1000` requests / second.
* `ffuf` will have a rate limit of `100` requests / second.
* `katana` will have a rate limit of `30` requests / second.
  {% endhint %}

***

## **Disabling default options**

Sometimes you might wish to omit passing the option and use the command defaults. You can set the option to `False` in order to do this.

```python
options = {
    'rate_limit': 1000, # reqs/s
    'ffuf.rate_limit': False, # explicitely disabling `rate_limit` option, will use ffuf defaults
}
```

***


# Configuration

... how to configure every aspect of how secator operates.

***

## Default configuration

`secator` is configured using a YAML config file. Every option has a default value, so the config file only needs the keys you want to change.

To print the full default configuration of your installed version, run:

```bash
secator config default
```

{% hint style="info" %}
Every key, its type, its default value and its environment variable are listed in the [Configuration reference](/getting-started/configuration/configuration-reference).
{% endhint %}

***

## Custom configuration

It is possible to override `secator`'s default configuration using a **user configuration file** located at `~/.secator/config.yml` .

The default user configuration is empty.

{% hint style="info" %}
The config file lives in the data folder (`~/.secator` by default). To use another data folder, and therefore another config file, set the `SECATOR_DIRS_DATA` environment variable.
{% endhint %}

### Get config

```bash
secator config get                         # get full config (with defaults)
secator config get --user                  # get user config (non-default values only)
secator config get wordlists.defaults.http # get a single key
```

### Edit config

#### Editing whole config

To modify `secator`'s user config, run:

```bash
secator config edit
```

This will open the YAML config in your default editor (using the`$EDITOR` env variable).

Upon saving your changes, the config will be validated by `pydantic` and saved to disk if it's valid.

{% hint style="warning" %}
If validation errors occur (for instance a wrong key, or wrong value type), you will get an error message such as:

```bash
❌ 1 validation error for SecatorConfig
  offline_test
    Extra inputs are not permitted 
      For further information visit https://errors.pydantic.dev/2.7/v/extra_forbidden
Hint: Run "secator config edit --resume" to edit your patch and fix issues.
```

The invalid patch will not be saved in the user config. This ensures the actual user config is always valid. Run `secator config edit --resume` to resume the edit to fix the issues, or skip the `--resume` flag to start over.
{% endhint %}

#### Editing specific config keys

To edit specific keys, use:

```bash
secator config set <path.to.config.key> <VALUE>
```

For instance, to print debug messages for the `celery` component:

```bash
secator config set debug celery
```

... or to set the `mongodb` addon URL:

```bash
secator config set addons.mongodb.url mongodb://mymongodbhost
```

List fields can be replaced, appended to, or have a single item removed:

```bash
secator config set drivers.defaults mongodb           # replace the list
secator config set drivers.defaults json --append     # append to the list
secator config unset drivers.defaults json            # remove one item from the list
```

Dict fields accept sub-keys:

```bash
secator config set wordlists.defaults.http mylist                # set a dict sub-key
secator config set workspaces.profiles.my_ws aggressive,passive  # set the default profiles of a workspace
secator config unset wordlists.defaults.http                     # remove a dict sub-key
```

#### Resetting config keys

To reset a key to its default value, use `secator config unset`:

```bash
secator config unset debug
```

### Env overrides

Values in the `secator` config can be overriden using environment variables. Environment variables are prefixed with `SECATOR_`, upper-cased, and use the dotted path of the key with dots replaced by underscores.

For instance, to override `debug`, run:

```bash
export SECATOR_DEBUG=celery
```

To override the default HTTP wordlist (`wordlists.defaults.http`):

```bash
export SECATOR_WORDLISTS_DEFAULTS_HTTP=/path/to/wordlist.txt
```

To use Redis as a Celery broker (`celery.broker_url`):

```bash
export SECATOR_CELERY_BROKER_URL=redis://localhost:6379/0
```

and so on. Environment variables take precedence over the config file. Unknown keys are ignored with a warning.

***

## Task overrides

`tasks.overrides` changes attributes of a specific task without touching its code. Keys are task names, values are the task attributes to override. Common attributes are:

| Attribute          | Description                                                                                                                                               |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `max_timeout`      | Max execution time of the task in seconds (overrides `celery.task_max_timeout` for this task).                                                            |
| `input_chunk_size` | Number of inputs per chunk when the task runs on workers (`-1` disables chunking).                                                                        |
| `profile`          | Worker queue the task is sent to (`small`, `medium`, `large`, `extra_large`), see [Distributed runs with Celery](/in-depth/distributed-runs-with-celery). |

{% tabs %}
{% tab title="CLI" %}

```bash
secator config set tasks.overrides.nmap.max_timeout 3600
secator config set tasks.overrides.search_vulns.profile large
```

{% endtab %}

{% tab title="YAML" %}

```yaml
tasks:
  overrides:
    nmap:
      max_timeout: 3600
    search_vulns:
      profile: large
```

{% endtab %}

{% tab title="Env vars" %}
Use `SECATOR_TASKS_OVERRIDES_<TASK>_<ATTRIBUTE>`. Task names containing underscores are supported:

```bash
export SECATOR_TASKS_OVERRIDES_NMAP_MAX_TIMEOUT=3600
export SECATOR_TASKS_OVERRIDES_SEARCH_VULNS_PROFILE=large
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
A `profile` override only applies to tasks with a static queue. Tasks that pick their queue dynamically (for instance `katana` in headless mode) keep their own routing.
{% endhint %}

***

## Debugging

The `debug` key prints internal debug messages for selected components. It accepts a comma-separated list of component names (prefix match), regexes, or `all`:

```bash
secator config set debug all            # everything
secator x httpx example.com             # ... run something
SECATOR_DEBUG=celery,hooks secator w host_recon example.com  # only for one run
SECATOR_DEBUG='cve.*' secator x nmap example.com             # regex
```

***


# Configuration reference

... or every configuration option, its default and its env var.

{% hint style="info" %}
This page is generated from secator's configuration models by `scripts/generate_config_reference.py` in the docs repository. To see the defaults of your installed version, run `secator config default`.
{% endhint %}

Set any key in `~/.secator/config.yml` (or with `secator config set <key> <value>`), or override it with its environment variable. See [Configuration](/getting-started/configuration) for how config files and overrides work.

***

## debug

Debug output filtering.

| Key     | Type  | Default | Env var         | Description                                                                                                 |
| ------- | ----- | ------- | --------------- | ----------------------------------------------------------------------------------------------------------- |
| `debug` | `str` | `""`    | `SECATOR_DEBUG` | Comma-separated debug components to print (`all` for everything, prefix match, or a regex such as `cve.*`). |

***

## dirs

Directories used by secator. Unset directories are created under `dirs.data`.

| Key                   | Type   | Default                      | Env var                       | Description                                                                                                 |
| --------------------- | ------ | ---------------------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `dirs.bin`            | `path` | `~/.local/bin`               | `SECATOR_DIRS_BIN`            | Where tool binaries are installed.                                                                          |
| `dirs.share`          | `path` | `~/.local/share`             | `SECATOR_DIRS_SHARE`          | Where shared tool data is installed.                                                                        |
| `dirs.data`           | `path` | `~/.secator`                 | `SECATOR_DIRS_DATA`           | Root data folder. `config.yml` is read from this folder, so change it with the `SECATOR_DIRS_DATA` env var. |
| `dirs.templates`      | `path` | `<dirs.data>/templates`      | `SECATOR_DIRS_TEMPLATES`      | Custom task, workflow, scan and profile templates.                                                          |
| `dirs.reports`        | `path` | `<dirs.data>/reports`        | `SECATOR_DIRS_REPORTS`        | Reports, one subfolder per workspace.                                                                       |
| `dirs.wordlists`      | `path` | `<dirs.data>/wordlists`      | `SECATOR_DIRS_WORDLISTS`      | Downloaded wordlists.                                                                                       |
| `dirs.cves`           | `path` | `<dirs.data>/cves`           | `SECATOR_DIRS_CVES`           | Cached CVE data.                                                                                            |
| `dirs.payloads`       | `path` | `<dirs.data>/payloads`       | `SECATOR_DIRS_PAYLOADS`       | Downloaded payloads.                                                                                        |
| `dirs.performance`    | `path` | `<dirs.data>/performance`    | `SECATOR_DIRS_PERFORMANCE`    | Profiling output (`-pyinstrument`, `-memray`).                                                              |
| `dirs.revshells`      | `path` | `<dirs.data>/revshells`      | `SECATOR_DIRS_REVSHELLS`      | Reverse shells data.                                                                                        |
| `dirs.queries`        | `path` | `<dirs.data>/queries`        | `SECATOR_DIRS_QUERIES`        | Query data.                                                                                                 |
| `dirs.celery`         | `path` | `<dirs.data>/celery`         | `SECATOR_DIRS_CELERY`         | Celery data folder.                                                                                         |
| `dirs.celery_data`    | `path` | `<dirs.data>/celery/data`    | `SECATOR_DIRS_CELERY_DATA`    | Filesystem broker data.                                                                                     |
| `dirs.celery_results` | `path` | `<dirs.data>/celery/results` | `SECATOR_DIRS_CELERY_RESULTS` | Filesystem result backend data.                                                                             |

***

## celery

Celery settings used for distributed runs (see [Distributed runs with Celery](/in-depth/distributed-runs-with-celery)).

| Key                                                          | Type    | Default         | Env var                                                              | Description                                                                                                                                        |
| ------------------------------------------------------------ | ------- | --------------- | -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `celery.broker_url`                                          | `str`   | `filesystem://` | `SECATOR_CELERY_BROKER_URL`                                          | Broker URL (`filesystem://`, `redis://host:6379/0`, `amqp://...`).                                                                                 |
| `celery.broker_pool_limit`                                   | `int`   | `10`            | `SECATOR_CELERY_BROKER_POOL_LIMIT`                                   | Max broker connections.                                                                                                                            |
| `celery.broker_connection_timeout`                           | `float` | `4.0`           | `SECATOR_CELERY_BROKER_CONNECTION_TIMEOUT`                           | Broker connection timeout (seconds).                                                                                                               |
| `celery.broker_visibility_timeout`                           | `int`   | `3600`          | `SECATOR_CELERY_BROKER_VISIBILITY_TIMEOUT`                           | Broker visibility timeout (seconds).                                                                                                               |
| `celery.broker_transport_options`                            | `str`   | `""`            | `SECATOR_CELERY_BROKER_TRANSPORT_OPTIONS`                            | Broker transport options (JSON string).                                                                                                            |
| `celery.override_default_logging`                            | `bool`  | `true`          | `SECATOR_CELERY_OVERRIDE_DEFAULT_LOGGING`                            | Replace Celery logging with secator logging.                                                                                                       |
| `celery.result_backend`                                      | `str`   | `""`            | `SECATOR_CELERY_RESULT_BACKEND`                                      | Result backend URL. Empty means `file://<dirs.celery_results>`.                                                                                    |
| `celery.result_backend_transport_options`                    | `str`   | `""`            | `SECATOR_CELERY_RESULT_BACKEND_TRANSPORT_OPTIONS`                    | Result backend transport options (JSON string).                                                                                                    |
| `celery.result_expires`                                      | `int`   | `86400`         | `SECATOR_CELERY_RESULT_EXPIRES`                                      | Result TTL (seconds).                                                                                                                              |
| `celery.redis_backend_health_check_interval`                 | `int`   | `0`             | `SECATOR_CELERY_REDIS_BACKEND_HEALTH_CHECK_INTERVAL`                 | Redis result backend health check interval (seconds); `0` disables it. Keep it at `0`: enabling it can abort workflows with a Redis `PubSubError`. |
| `celery.task_acks_late`                                      | `bool`  | `false`         | `SECATOR_CELERY_TASK_ACKS_LATE`                                      | Acknowledge tasks after they finish instead of when they start.                                                                                    |
| `celery.task_send_sent_event`                                | `bool`  | `false`         | `SECATOR_CELERY_TASK_SEND_SENT_EVENT`                                | Emit task-sent events.                                                                                                                             |
| `celery.task_reject_on_worker_lost`                          | `bool`  | `false`         | `SECATOR_CELERY_TASK_REJECT_ON_WORKER_LOST`                          | Re-queue a task when its worker dies.                                                                                                              |
| `celery.task_max_retries`                                    | `int`   | `-1`            | `SECATOR_CELERY_TASK_MAX_RETRIES`                                    | Max redeliveries of a task whose worker was lost before it is abandoned; `-1` disables the cap.                                                    |
| `celery.task_max_timeout`                                    | `int`   | `-1`            | `SECATOR_CELERY_TASK_MAX_TIMEOUT`                                    | Max task duration (seconds); `-1` = unlimited. Per-task override: `tasks.overrides.<task>.max_timeout`.                                            |
| `celery.task_memory_limit_mb`                                | `int`   | `-1`            | `SECATOR_CELERY_TASK_MEMORY_LIMIT_MB`                                | Max task memory (MB); `-1` = unlimited.                                                                                                            |
| `celery.worker_max_tasks_per_child`                          | `int`   | `20`            | `SECATOR_CELERY_WORKER_MAX_TASKS_PER_CHILD`                          | Tasks a worker process runs before it is recycled.                                                                                                 |
| `celery.worker_prefetch_multiplier`                          | `int`   | `1`             | `SECATOR_CELERY_WORKER_PREFETCH_MULTIPLIER`                          | Tasks prefetched per worker process.                                                                                                               |
| `celery.worker_send_task_events`                             | `bool`  | `false`         | `SECATOR_CELERY_WORKER_SEND_TASK_EVENTS`                             | Workers emit task events.                                                                                                                          |
| `celery.worker_kill_after_task`                              | `bool`  | `false`         | `SECATOR_CELERY_WORKER_KILL_AFTER_TASK`                              | Stop the worker after each task.                                                                                                                   |
| `celery.worker_kill_after_idle_seconds`                      | `int`   | `-1`            | `SECATOR_CELERY_WORKER_KILL_AFTER_IDLE_SECONDS`                      | Stop the worker after this many idle seconds; `-1` = never.                                                                                        |
| `celery.worker_command_verbose`                              | `bool`  | `false`         | `SECATOR_CELERY_WORKER_COMMAND_VERBOSE`                              | Print the commands run by the worker.                                                                                                              |
| `celery.worker_cancel_long_running_tasks_on_connection_loss` | `bool`  | `false`         | `SECATOR_CELERY_WORKER_CANCEL_LONG_RUNNING_TASKS_ON_CONNECTION_LOSS` | Cancel in-flight tasks when the worker loses its broker connection (use with `task_acks_late` and `task_reject_on_worker_lost`).                   |

***

## cli

CLI behaviour.

| Key                                 | Type        | Default                                                    | Env var                                     | Description                                                                                 |
| ----------------------------------- | ----------- | ---------------------------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `cli.github_token`                  | `str`       | `""`                                                       | `SECATOR_CLI_GITHUB_TOKEN`                  | GitHub token used to avoid rate limits when installing tools (defaults to `$GITHUB_TOKEN`). |
| `cli.record`                        | `bool`      | `false`                                                    | `SECATOR_CLI_RECORD`                        | Record CLI sessions.                                                                        |
| `cli.stdin_timeout`                 | `int`       | `1000`                                                     | `SECATOR_CLI_STDIN_TIMEOUT`                 | Time to wait for stdin input (ms).                                                          |
| `cli.show_http_response_headers`    | `bool`      | `false`                                                    | `SECATOR_CLI_SHOW_HTTP_RESPONSE_HEADERS`    | Show HTTP response headers in console output.                                               |
| `cli.show_command_output`           | `bool`      | `false`                                                    | `SECATOR_CLI_SHOW_COMMAND_OUTPUT`           | Show raw tool output.                                                                       |
| `cli.exclude_http_response_headers` | `list[str]` | `[connection, content_type, content_length, date, server]` | `SECATOR_CLI_EXCLUDE_HTTP_RESPONSE_HEADERS` | Response headers hidden from console output.                                                |
| `cli.date_format`                   | `str`       | `%m/%d/%Y`                                                 | `SECATOR_CLI_DATE_FORMAT`                   | Date format in CLI output (`%d/%m/%Y` for European format).                                 |

***

## runners

Settings applied to every task, workflow and scan.

| Key                                 | Type   | Default | Env var                                     | Description                                                                               |
| ----------------------------------- | ------ | ------- | ------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `runners.input_chunk_size`          | `int`  | `100`   | `SECATOR_RUNNERS_INPUT_CHUNK_SIZE`          | Inputs per chunk when a task is split across workers; `-1` disables chunking.             |
| `runners.progress_update_frequency` | `int`  | `20`    | `SECATOR_RUNNERS_PROGRESS_UPDATE_FREQUENCY` | Progress update interval (seconds).                                                       |
| `runners.stat_update_frequency`     | `int`  | `20`    | `SECATOR_RUNNERS_STAT_UPDATE_FREQUENCY`     | Runtime stats update interval (seconds).                                                  |
| `runners.monitor_worker`            | `bool` | `false` | `SECATOR_RUNNERS_MONITOR_WORKER`            | Also report the worker process itself in runtime stats (useful with one task per worker). |
| `runners.backend_update_frequency`  | `int`  | `5`     | `SECATOR_RUNNERS_BACKEND_UPDATE_FREQUENCY`  | Interval between result pushes to drivers (seconds).                                      |
| `runners.poll_frequency`            | `int`  | `5`     | `SECATOR_RUNNERS_POLL_FREQUENCY`            | Interval between result polls when running in a worker (seconds).                         |
| `runners.skip_cve_search`           | `bool` | `false` | `SECATOR_RUNNERS_SKIP_CVE_SEARCH`           | Skip CVE lookups.                                                                         |
| `runners.skip_exploit_search`       | `bool` | `false` | `SECATOR_RUNNERS_SKIP_EXPLOIT_SEARCH`       | Skip exploit lookups.                                                                     |
| `runners.skip_cve_low_confidence`   | `bool` | `false` | `SECATOR_RUNNERS_SKIP_CVE_LOW_CONFIDENCE`   | Skip CVE lookups for low-confidence matches.                                              |
| `runners.remove_duplicates`         | `bool` | `false` | `SECATOR_RUNNERS_REMOVE_DUPLICATES`         | Remove duplicate findings.                                                                |
| `runners.threads`                   | `int`  | `50`    | `SECATOR_RUNNERS_THREADS`                   | Default number of threads.                                                                |
| `runners.prompt_timeout`            | `int`  | `20`    | `SECATOR_RUNNERS_PROMPT_TIMEOUT`            | Timeout for interactive prompts (seconds).                                                |
| `runners.chunk_rate_limit`          | `bool` | `true`  | `SECATOR_RUNNERS_CHUNK_RATE_LIMIT`          | Divide `rate_limit` by the number of chunks when a task is chunked.                       |

***

## http

HTTP and proxy settings.

| Key                            | Type   | Default                                                                                                                       | Env var                                | Description                                       |
| ------------------------------ | ------ | ----------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | ------------------------------------------------- |
| `http.socks5_proxy`            | `str`  | `socks5://127.0.0.1:9050`                                                                                                     | `SECATOR_HTTP_SOCKS5_PROXY`            | SOCKS5 proxy used by `-proxy auto` (e.g. Tor).    |
| `http.http_proxy`              | `str`  | `https://127.0.0.1:9080`                                                                                                      | `SECATOR_HTTP_HTTP_PROXY`              | HTTP proxy used by `-proxy auto`.                 |
| `http.store_responses`         | `bool` | `true`                                                                                                                        | `SECATOR_HTTP_STORE_RESPONSES`         | Store HTTP responses on disk.                     |
| `http.response_max_size_bytes` | `int`  | `100000`                                                                                                                      | `SECATOR_HTTP_RESPONSE_MAX_SIZE_BYTES` | Max stored response size (bytes).                 |
| `http.proxychains_command`     | `str`  | `proxychains`                                                                                                                 | `SECATOR_HTTP_PROXYCHAINS_COMMAND`     | Proxychains binary used by `-proxy proxychains`.  |
| `http.freeproxy_timeout`       | `int`  | `1`                                                                                                                           | `SECATOR_HTTP_FREEPROXY_TIMEOUT`       | Timeout used when testing free proxies (seconds). |
| `http.default_header`          | `str`  | `User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36` | `SECATOR_HTTP_DEFAULT_HEADER`          | Default header sent by HTTP tasks.                |

***

## tasks

Task settings.

| Key               | Type                        | Default                | Env var                                 | Description                                                                                                                                       |
| ----------------- | --------------------------- | ---------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tasks.exporters` | `list[str]`                 | `[csv, txt, markdown]` | `SECATOR_TASKS_EXPORTERS`               | Default exporters for tasks.                                                                                                                      |
| `tasks.overrides` | `dict[str, dict[str, any]]` | `{}`                   | `SECATOR_TASKS_OVERRIDES_<TASK>_<ATTR>` | Per-task attribute overrides, e.g. `tasks.overrides.nmap.max_timeout: 600` (see [Task overrides](/getting-started/configuration#task-overrides)). |

***

## workflows

Workflow settings.

| Key                   | Type        | Default                | Env var                       | Description                      |
| --------------------- | ----------- | ---------------------- | ----------------------------- | -------------------------------- |
| `workflows.exporters` | `list[str]` | `[csv, txt, markdown]` | `SECATOR_WORKFLOWS_EXPORTERS` | Default exporters for workflows. |

***

## scans

Scan settings.

| Key               | Type        | Default                | Env var                   | Description                  |
| ----------------- | ----------- | ---------------------- | ------------------------- | ---------------------------- |
| `scans.exporters` | `list[str]` | `[csv, txt, markdown]` | `SECATOR_SCANS_EXPORTERS` | Default exporters for scans. |

***

## payloads

Payloads served by `secator u serve`.

| Key                  | Type             | Default                         | Env var                            | Description                                                   |
| -------------------- | ---------------- | ------------------------------- | ---------------------------------- | ------------------------------------------------------------- |
| `payloads.templates` | `dict[str, str]` | `lse`, `linpeas`, `sudo_killer` | `SECATOR_PAYLOADS_TEMPLATES_<KEY>` | Payload name -> URL, `git+https://` repository or local path. |

***

## wordlists

Wordlists used by fuzzing and brute-force tasks.

| Key                   | Type                   | Default                                                                            | Env var                             | Description                                               |
| --------------------- | ---------------------- | ---------------------------------------------------------------------------------- | ----------------------------------- | --------------------------------------------------------- |
| `wordlists.defaults`  | `dict[str, str]`       | `http`, `dns`, `http_params`                                                       | `SECATOR_WORDLISTS_DEFAULTS_<KEY>`  | Default wordlist per type (`http`, `dns`, `http_params`). |
| `wordlists.templates` | `dict[str, str]`       | `bo0m_fuzz`, `combined_subdomains`, `directory_list_small`, `burp-parameter-names` | `SECATOR_WORDLISTS_TEMPLATES_<KEY>` | Wordlist name -> URL or local path.                       |
| `wordlists.lists`     | `dict[str, list[str]]` | `{}`                                                                               | `SECATOR_WORDLISTS_LISTS_<KEY>`     | Inline wordlists (name -> list of words).                 |

***

## profiles

Profiles (see [Profiles](/in-depth/concepts/profiles)).

| Key                 | Type        | Default | Env var                     | Description                    |
| ------------------- | ----------- | ------- | --------------------------- | ------------------------------ |
| `profiles.defaults` | `list[str]` | `[]`    | `SECATOR_PROFILES_DEFAULTS` | Profiles applied to every run. |

***

## drivers

Drivers (see [Drivers](/in-depth/concepts/drivers)).

| Key                | Type        | Default | Env var                    | Description                   |
| ------------------ | ----------- | ------- | -------------------------- | ----------------------------- |
| `drivers.defaults` | `list[str]` | `[]`    | `SECATOR_DRIVERS_DEFAULTS` | Drivers applied to every run. |

***

## workspaces

Workspaces (see [Workspaces](/in-depth/concepts/workspaces)). `workspaces.routes` is deprecated and ignored (a one-time warning is printed if your config still sets it).

| Key                   | Type                   | Default | Env var                             | Description                                                                  |
| --------------------- | ---------------------- | ------- | ----------------------------------- | ---------------------------------------------------------------------------- |
| `workspaces.current`  | `str`                  | `""`    | `SECATOR_WORKSPACES_CURRENT`        | Current workspace (set with `secator ws use <name>`). Empty means `default`. |
| `workspaces.profiles` | `dict[str, list[str]]` | `{}`    | `SECATOR_WORKSPACES_PROFILES_<KEY>` | Default profiles per workspace: workspace -> list of profiles.               |

***

## addons

Addon settings. Install addons with `secator install addons <name>`.

| Key                                          | Type        | Default                                                                                                    | Env var                                              | Description                                                                                                            |
| -------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `addons.gdrive.enabled`                      | `bool`      | `false`                                                                                                    | `SECATOR_ADDONS_GDRIVE_ENABLED`                      | Enable the Google Drive exporter.                                                                                      |
| `addons.gdrive.drive_parent_folder_id`       | `str`       | `""`                                                                                                       | `SECATOR_ADDONS_GDRIVE_DRIVE_PARENT_FOLDER_ID`       | Parent Google Drive folder ID.                                                                                         |
| `addons.gdrive.credentials_path`             | `str`       | `""`                                                                                                       | `SECATOR_ADDONS_GDRIVE_CREDENTIALS_PATH`             | Path to the service account credentials JSON.                                                                          |
| `addons.gcs.enabled`                         | `bool`      | `false`                                                                                                    | `SECATOR_ADDONS_GCS_ENABLED`                         | Enable the Google Cloud Storage driver.                                                                                |
| `addons.gcs.bucket_name`                     | `str`       | `""`                                                                                                       | `SECATOR_ADDONS_GCS_BUCKET_NAME`                     | Bucket name.                                                                                                           |
| `addons.gcs.credentials_path`                | `str`       | `""`                                                                                                       | `SECATOR_ADDONS_GCS_CREDENTIALS_PATH`                | Optional path to credentials JSON (Application Default Credentials are used otherwise).                                |
| `addons.worker.enabled`                      | `bool`      | `false`                                                                                                    | `SECATOR_ADDONS_WORKER_ENABLED`                      | Enable the worker addon.                                                                                               |
| `addons.mongodb.enabled`                     | `bool`      | `false`                                                                                                    | `SECATOR_ADDONS_MONGODB_ENABLED`                     | Enable the MongoDB driver.                                                                                             |
| `addons.mongodb.url`                         | `str`       | `mongodb://localhost`                                                                                      | `SECATOR_ADDONS_MONGODB_URL`                         | MongoDB connection URL.                                                                                                |
| `addons.mongodb.update_frequency`            | `int`       | `60`                                                                                                       | `SECATOR_ADDONS_MONGODB_UPDATE_FREQUENCY`            | Min interval between runner updates (seconds).                                                                         |
| `addons.mongodb.max_pool_size`               | `int`       | `10`                                                                                                       | `SECATOR_ADDONS_MONGODB_MAX_POOL_SIZE`               | Max connection pool size.                                                                                              |
| `addons.mongodb.server_selection_timeout_ms` | `int`       | `5000`                                                                                                     | `SECATOR_ADDONS_MONGODB_SERVER_SELECTION_TIMEOUT_MS` | Server selection timeout (ms).                                                                                         |
| `addons.mongodb.max_items`                   | `int`       | `-1`                                                                                                       | `SECATOR_ADDONS_MONGODB_MAX_ITEMS`                   | Max items loaded per query; `-1` = unlimited.                                                                          |
| `addons.mongodb.duplicate_exclude_types`     | `list[str]` | `[info, warning, error, stat]`                                                                             | `SECATOR_ADDONS_MONGODB_DUPLICATE_EXCLUDE_TYPES`     | Output types never considered for duplicate tagging.                                                                   |
| `addons.mongodb.duplicate_main_copy_fields`  | `list[str]` | `[screenshot_path, stored_response_path, is_false_positive, is_acknowledged, verified, status, tags, poc]` | `SECATOR_ADDONS_MONGODB_DUPLICATE_MAIN_COPY_FIELDS`  | Fields carried over from a previous copy of a finding onto the newly found one (when empty there), e.g. triage status. |
| `addons.sqlite.enabled`                      | `bool`      | `false`                                                                                                    | `SECATOR_ADDONS_SQLITE_ENABLED`                      | Enable the SQLite driver.                                                                                              |
| `addons.sqlite.path`                         | `str`       | `""`                                                                                                       | `SECATOR_ADDONS_SQLITE_PATH`                         | Database file path. Empty means `<dirs.data>/secator.db`.                                                              |
| `addons.sqlite.busy_timeout_ms`              | `int`       | `5000`                                                                                                     | `SECATOR_ADDONS_SQLITE_BUSY_TIMEOUT_MS`              | SQLite busy timeout (ms).                                                                                              |
| `addons.sqlite.max_items`                    | `int`       | `-1`                                                                                                       | `SECATOR_ADDONS_SQLITE_MAX_ITEMS`                    | Max items loaded per query; `-1` = unlimited.                                                                          |
| `addons.sqlite.duplicate_main_copy_fields`   | `list[str]` | `[screenshot_path, stored_response_path, is_false_positive, is_acknowledged, verified, status, tags, poc]` | `SECATOR_ADDONS_SQLITE_DUPLICATE_MAIN_COPY_FIELDS`   | Fields carried over from a previous copy of a finding onto the newly found one (when empty there), e.g. triage status. |
| `addons.vulners.enabled`                     | `bool`      | `false`                                                                                                    | `SECATOR_ADDONS_VULNERS_ENABLED`                     | Enable the Vulners CVE provider.                                                                                       |
| `addons.vulners.api_key`                     | `str`       | `""`                                                                                                       | `SECATOR_ADDONS_VULNERS_API_KEY`                     | Vulners API key.                                                                                                       |
| `addons.discord.enabled`                     | `bool`      | `false`                                                                                                    | `SECATOR_ADDONS_DISCORD_ENABLED`                     | Enable the Discord driver.                                                                                             |
| `addons.discord.webhook_url`                 | `str`       | `""`                                                                                                       | `SECATOR_ADDONS_DISCORD_WEBHOOK_URL`                 | Discord webhook URL.                                                                                                   |
| `addons.discord.bot_token`                   | `str`       | `""`                                                                                                       | `SECATOR_ADDONS_DISCORD_BOT_TOKEN`                   | Discord bot token (alternative to the webhook).                                                                        |
| `addons.discord.send_runner_updates`         | `bool`      | `true`                                                                                                     | `SECATOR_ADDONS_DISCORD_SEND_RUNNER_UPDATES`         | Send runner start / end notifications.                                                                                 |
| `addons.discord.send_findings`               | `bool`      | `true`                                                                                                     | `SECATOR_ADDONS_DISCORD_SEND_FINDINGS`               | Send finding notifications.                                                                                            |
| `addons.discord.finding_types`               | `list[str]` | `[vulnerability]`                                                                                          | `SECATOR_ADDONS_DISCORD_FINDING_TYPES`               | Output types to notify.                                                                                                |
| `addons.discord.min_severity`                | `str`       | `high`                                                                                                     | `SECATOR_ADDONS_DISCORD_MIN_SEVERITY`                | Minimum vulnerability severity to notify.                                                                              |
| `addons.api.enabled`                         | `bool`      | `false`                                                                                                    | `SECATOR_ADDONS_API_ENABLED`                         | Enable the `api` driver.                                                                                               |
| `addons.api.url`                             | `str`       | see `secator config default`                                                                               | `SECATOR_ADDONS_API_URL`                             | Base URL of the HTTP API.                                                                                              |
| `addons.api.key`                             | `str`       | `""`                                                                                                       | `SECATOR_ADDONS_API_KEY`                             | API key.                                                                                                               |
| `addons.api.header_name`                     | `str`       | `Bearer`                                                                                                   | `SECATOR_ADDONS_API_HEADER_NAME`                     | Authorization scheme sent before the key.                                                                              |
| `addons.api.force_ssl`                       | `bool`      | `true`                                                                                                     | `SECATOR_ADDONS_API_FORCE_SSL`                       | Verify TLS certificates.                                                                                               |
| `addons.api.timeout`                         | `int`       | `60`                                                                                                       | `SECATOR_ADDONS_API_TIMEOUT`                         | Request timeout (seconds).                                                                                             |
| `addons.api.org_id`                          | `int`       | `null`                                                                                                     | `SECATOR_ADDONS_API_ORG_ID`                          | Organization ID to query (admins only); defaults to the user's own.                                                    |
| `addons.api.runner_create_endpoint`          | `str`       | `runners`                                                                                                  | `SECATOR_ADDONS_API_RUNNER_CREATE_ENDPOINT`          | Endpoint path (relative to `url`).                                                                                     |
| `addons.api.runner_get_endpoint`             | `str`       | `runner/{runner_id}`                                                                                       | `SECATOR_ADDONS_API_RUNNER_GET_ENDPOINT`             | Endpoint path (relative to `url`).                                                                                     |
| `addons.api.runner_update_endpoint`          | `str`       | `runner/{runner_id}`                                                                                       | `SECATOR_ADDONS_API_RUNNER_UPDATE_ENDPOINT`          | Endpoint path (relative to `url`).                                                                                     |
| `addons.api.finding_create_endpoint`         | `str`       | `findings`                                                                                                 | `SECATOR_ADDONS_API_FINDING_CREATE_ENDPOINT`         | Endpoint path (relative to `url`).                                                                                     |
| `addons.api.finding_update_endpoint`         | `str`       | `finding/{finding_id}`                                                                                     | `SECATOR_ADDONS_API_FINDING_UPDATE_ENDPOINT`         | Endpoint path (relative to `url`).                                                                                     |
| `addons.api.finding_search_endpoint`         | `str`       | `findings/_search`                                                                                         | `SECATOR_ADDONS_API_FINDING_SEARCH_ENDPOINT`         | Endpoint path (relative to `url`).                                                                                     |
| `addons.api.workspace_list_endpoint`         | `str`       | `workspaces`                                                                                               | `SECATOR_ADDONS_API_WORKSPACE_LIST_ENDPOINT`         | Endpoint path (relative to `url`).                                                                                     |
| `addons.api.workspace_create_endpoint`       | `str`       | `workspaces`                                                                                               | `SECATOR_ADDONS_API_WORKSPACE_CREATE_ENDPOINT`       | Endpoint path (relative to `url`).                                                                                     |
| `addons.api.workspace_get_endpoint`          | `str`       | `workspace/{workspace_id}`                                                                                 | `SECATOR_ADDONS_API_WORKSPACE_GET_ENDPOINT`          | Endpoint path (relative to `url`).                                                                                     |
| `addons.api.workspace_delete_endpoint`       | `str`       | `workspace/{workspace_id}`                                                                                 | `SECATOR_ADDONS_API_WORKSPACE_DELETE_ENDPOINT`       | Endpoint path (relative to `url`).                                                                                     |
| `addons.api.runners_list_endpoint`           | `str`       | `runners/any`                                                                                              | `SECATOR_ADDONS_API_RUNNERS_LIST_ENDPOINT`           | Endpoint path (relative to `url`).                                                                                     |
| `addons.api.runner_delete_endpoint`          | `str`       | `runner/{runner_id}?type={runner_type}`                                                                    | `SECATOR_ADDONS_API_RUNNER_DELETE_ENDPOINT`          | Endpoint path (relative to `url`).                                                                                     |
| `addons.ai.enabled`                          | `bool`      | `false`                                                                                                    | `SECATOR_ADDONS_AI_ENABLED`                          | Enable the `ai` task.                                                                                                  |
| `addons.ai.api_key`                          | `str`       | `""`                                                                                                       | `SECATOR_ADDONS_AI_API_KEY`                          | LLM provider API key.                                                                                                  |
| `addons.ai.api_base`                         | `str`       | `""`                                                                                                       | `SECATOR_ADDONS_AI_API_BASE`                         | Custom LLM API base URL (e.g. a local OpenAI-compatible server).                                                       |
| `addons.ai.custom_disallow_config_token`     | `bool`      | `false`                                                                                                    | `SECATOR_ADDONS_AI_CUSTOM_DISALLOW_CONFIG_TOKEN`     | When a caller sets a custom `api_base`, require it to also pass its own API key instead of reusing `api_key`.          |
| `addons.ai.default_model`                    | `str`       | `claude-sonnet-4-6`                                                                                        | `SECATOR_ADDONS_AI_DEFAULT_MODEL`                    | Default model.                                                                                                         |
| `addons.ai.intent_model`                     | `str`       | `claude-haiku-4-5`                                                                                         | `SECATOR_ADDONS_AI_INTENT_MODEL`                     | Lightweight model used for intent detection.                                                                           |
| `addons.ai.temperature`                      | `float`     | `0.7`                                                                                                      | `SECATOR_ADDONS_AI_TEMPERATURE`                      | Sampling temperature.                                                                                                  |
| `addons.ai.max_tokens`                       | `int`       | `30000`                                                                                                    | `SECATOR_ADDONS_AI_MAX_TOKENS`                       | Max tokens per response.                                                                                               |
| `addons.ai.max_tokens_total`                 | `int`       | `100000`                                                                                                   | `SECATOR_ADDONS_AI_MAX_TOKENS_TOTAL`                 | Max tokens kept in the conversation (oldest messages are truncated).                                                   |
| `addons.ai.max_results`                      | `int`       | `500`                                                                                                      | `SECATOR_ADDONS_AI_MAX_RESULTS`                      | Max results passed to the model.                                                                                       |
| `addons.ai.max_iterations`                   | `int`       | `25`                                                                                                       | `SECATOR_ADDONS_AI_MAX_ITERATIONS`                   | Max agent iterations per run; `-1` (or `<= 0`) disables the cap.                                                       |
| `addons.ai.context_window`                   | `int`       | `128000`                                                                                                   | `SECATOR_ADDONS_AI_CONTEXT_WINDOW`                   | Model context window used when the provider does not report one.                                                       |
| `addons.ai.user_response_timeout`            | `int`       | `600`                                                                                                      | `SECATOR_ADDONS_AI_USER_RESPONSE_TIMEOUT`            | Time to wait for a user answer (seconds).                                                                              |
| `addons.ai.encrypt_pii`                      | `bool`      | `true`                                                                                                     | `SECATOR_ADDONS_AI_ENCRYPT_PII`                      | Encrypt sensitive data before sending it to the model.                                                                 |
| `addons.ai.permissions`                      | `dict`      | see [AI task](/in-depth/ai-task#permission-engine)                                                         | `SECATOR_ADDONS_AI_PERMISSIONS`                      | Permission rules (`allow` / `deny` / `ask`), see [AI task](/in-depth/ai-task).                                         |

***

## security

Security controls.

| Key                                | Type   | Default | Env var                                    | Description                                                                           |
| ---------------------------------- | ------ | ------- | ------------------------------------------ | ------------------------------------------------------------------------------------- |
| `security.allow_local_file_access` | `bool` | `true`  | `SECATOR_SECURITY_ALLOW_LOCAL_FILE_ACCESS` | Allow wordlists / payloads to be loaded from local files outside `dirs.data`.         |
| `security.auto_install_commands`   | `bool` | `true`  | `SECATOR_SECURITY_AUTO_INSTALL_COMMANDS`   | Install missing tools automatically on first use.                                     |
| `security.force_source_install`    | `bool` | `false` | `SECATOR_SECURITY_FORCE_SOURCE_INSTALL`    | Build tools from source instead of downloading binaries.                              |
| `security.prompt_sudo_password`    | `bool` | `true`  | `SECATOR_SECURITY_PROMPT_SUDO_PASSWORD`    | Prompt for the sudo password when a task needs it.                                    |
| `security.sudo_password`           | `str`  | `""`    | `SECATOR_SECURITY_SUDO_PASSWORD`           | Non-interactive sudo password, for headless workers (prefer the env var).             |
| `security.shell_isolated`          | `bool` | `false` | `SECATOR_SECURITY_SHELL_ISOLATED`          | Default for the `ai` task `--isolated` flag (run shell commands in a Docker sandbox). |
| `security.scope_hard_deny`         | `bool` | `false` | `SECATOR_SECURITY_SCOPE_HARD_DENY`         | Deny out-of-scope AI targets outright instead of asking for approval.                 |

***

## providers

Default enrichment providers.

| Key                  | Type             | Default                                                                                      | Env var                            | Description                                                                                           |
| -------------------- | ---------------- | -------------------------------------------------------------------------------------------- | ---------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `providers.defaults` | `dict[str, str]` | <p><code>cve: circl</code><br><code>exploit: exploitdb</code><br><code>ghsa: ghsa</code></p> | `SECATOR_PROVIDERS_DEFAULTS_<KEY>` | Default provider per data type (`cve`: `circl` or `vulners`, `exploit`: `exploitdb`, `ghsa`: `ghsa`). |

***

## queries

Saved queries for `secator query` (see [Querying results](/getting-started/querying-results)).

| Key       | Type             | Default | Env var                 | Description                              |
| --------- | ---------------- | ------- | ----------------------- | ---------------------------------------- |
| `queries` | `dict[str, str]` | `{}`    | `SECATOR_QUERIES_<KEY>` | Saved queries: name -> query expression. |

***

## offline\_mode

Offline mode.

| Key            | Type   | Default | Env var                | Description                                                                   |
| -------------- | ------ | ------- | ---------------------- | ----------------------------------------------------------------------------- |
| `offline_mode` | `bool` | `false` | `SECATOR_OFFLINE_MODE` | Disable all network downloads and lookups (wordlists, payloads, tools, CVEs). |


# Querying results & reports

... or how to find, filter and reuse everything secator has found.

Every task, workflow and scan you run with `secator` is saved as a **report**. You can then list them, inspect them, and query findings across all of them, without running anything again.

This page covers the `secator report` (`secator r`) and `secator query` (`secator q`) commands.

***

## Where reports are stored

By default, reports are saved under `~/.secator/reports/`, grouped by [Workspaces](/in-depth/concepts/workspaces) and then by runner type:

```
~/.secator/reports/
└── <workspace>/
    ├── tasks/
    │   ├── 0/
    │   │   ├── report.json        # runner info + results
    │   │   ├── report_url.csv     # exporter outputs (one file per output type)
    │   │   ├── report_url.txt
    │   │   ├── .inputs/           # input files passed to the tools
    │   │   └── .outputs/          # raw tool outputs (screenshots, responses...)
    │   └── 1/
    ├── workflows/
    │   └── 0/
    └── scans/
        └── 0/
```

Each run gets the next free number in its folder. A report is referred to by its **runner path**, `<type>/<id>`, for example `tasks/0`, `workflows/3` or `scans/12`. All commands below use this format.

{% hint style="info" %}
The reports folder follows the `dirs.data` setting. Set `SECATOR_DIRS_DATA=/path/to/folder` to use a different data folder, e.g. one per engagement. See [Configuration](/getting-started/configuration).
{% endhint %}

***

## Listing reports

Use `secator report list` (or `secator r list`) to list your reports:

```bash
secator r list
```

The table shows the workspace, runner name, runner path (`Id`), targets, profiles, start / end dates, elapsed time, status, and vulnerability counts by severity.

Without `-w`, reports from **all** workspaces are listed. Use these options to narrow the list:

| Option                     | Description                                                                                                           |
| -------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `-w`, `-ws`, `--workspace` | Only list reports from this workspace.                                                                                |
| `-r`, `--runner-type`      | Only list one runner type: `task`, `workflow` or `scan`.                                                              |
| `-d`, `--time-delta`       | Only list reports newer than a time delta, e.g. `26m`, `1d`, `1y`.                                                    |
| `--status`                 | Filter by runner status, as a case-insensitive regex, e.g. `SUCCESS`, `FAIL`, `'(RUNNING\|FAILURE)'`.                 |
| `-i`, `--interesting`      | Only list reports that have vulnerabilities.                                                                          |
| `--show-children`          | Also list nested sub-tasks / sub-workflows. By default, only outermost runners are shown.                             |
| `--show-all`               | Show all columns, including the report path.                                                                          |
| `--driver`                 | Query backend to read from: `local`, `mongodb`, `api` or `sqlite` (see [#query-backends](#query-backends "mention")). |

{% tabs %}
{% tab title="Recent scans" %}
List the scans run in the `mydomain` workspace in the last two days:

```bash
secator r list -w mydomain -r scan -d 2d
```

{% endtab %}

{% tab title="Failed runs" %}
List runs that failed or are still running:

```bash
secator r list --status '(RUNNING|FAILURE)'
```

{% endtab %}

{% tab title="With vulnerabilities" %}
List only reports that found vulnerabilities, with their paths on disk:

```bash
secator r list -i --show-all
```

{% endtab %}
{% endtabs %}

{% hint style="warning" %}
`-i` and `-d` only work with the `local` driver. `--show-children` only has an effect with the `mongodb` and `api` drivers, because local reports don't store sub-runners separately.
{% endhint %}

***

## Inspecting a report

`secator report info <RUNNER_PATH>` shows the metadata of one run: name, targets, options, status, timings, context, plus its errors and warnings.

```bash
secator r info scans/0
secator r info tasks/12 -w mydomain
```

By default, long lists are truncated, the verbose `config` and `output` fields are hidden, and only the last error / warning is shown. Use `--show-all` to see everything:

```bash
secator r info scans/0 --show-all
```

***

## Showing report results

`secator report show` prints the results of one or more reports. Pass runner paths as a comma-separated `REPORT_QUERY`:

```bash
secator r show tasks/0                 # results of task 0
secator r show scans/5,tasks/3         # results of scan 5 and task 3
secator r show -w mydomain             # all results from the 'mydomain' workspace
secator r show                         # all results from the current workspace
```

Without `-w`, the current workspace is used (see [Workspaces](/in-depth/concepts/workspaces)).

{% hint style="info" %}
`target` and `ai` results are hidden by default because they are verbose. To see them, query their type explicitly, e.g. `-q target`.
{% endhint %}

### Filtering with `-q`

Use `-q` / `--query` to filter results with an expression (see [#query-syntax](#query-syntax "mention")):

```bash
secator r show scans/5 -q "port.state == 'open'"
secator r show -q "vulnerability.severity in [critical, high]"
```

### Formatting with `-f`

Use `-f` / `--format` to print only the fields you need, one line per finding. This works well for piping into other tools.

{% tabs %}
{% tab title="Braces" %}
Use `{type.field}` placeholders to build a line from several fields:

```bash
secator r show -q port -f "{port.host}:{port.port}"
secator r show -q url -f "{url.url} [{url.status_code}]"
```

When the results contain a single output type, you can drop the type prefix:

```bash
secator r show -q url -f "{url} {status_code} {title}"
```

{% endtab %}

{% tab title="Dot-path" %}
Use `type.field` (no braces) to print a single field:

```bash
secator r show -q vulnerability -f vulnerability.matched_at
```

Nested fields work too, e.g. for values stored in `extra_data`:

```bash
secator r show -q tag -f extra_data.value
```

{% endtab %}

{% tab title="Several types" %}
Separate one format per output type with `||`. Each finding is formatted with the spec that matches its type:

```bash
secator r show scans/5 -f "{port.host}:{port.port} || vulnerability.matched_at"
```

{% endtab %}

{% tab title="Template file" %}
`-f` also accepts a path to a file containing the format. `\n` and `\t` are expanded:

```bash
echo '{url.url}\n  title: {url.title}' > url.fmt
secator r show -q url -f url.fmt
```

{% endtab %}
{% endtabs %}

{% hint style="warning" %}
`-f` only works with the `console` exporter. It can't be combined with `-o json`, `-o csv`, and so on.
{% endhint %}

### Sorting, counting and grouping

These options reshape the output after the query runs:

| Option           | Description                                                                                                                                                    |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `-l`, `--limit`  | Limit the number of results (`0` = no limit).                                                                                                                  |
| `--sort <field>` | Sort by a field, numeric-aware. Prefix the field with `-` to sort in descending order, e.g. `--sort port` or `--sort -cvss_score`.                             |
| `--uniq`         | Drop duplicate `--format` values.                                                                                                                              |
| `--count`        | Group identical `--format` values and prefix each one with its number of occurrences, most frequent first. With `--sort`, values are ordered by value instead. |
| `--group`        | Group findings by their output type's default field(s) and list the related values on one row (see below).                                                     |

{% tabs %}
{% tab title="Top ports" %}

```bash
secator r show -q port -f port --count -l 15
```

{% endtab %}

{% tab title="Unique hosts" %}

```bash
secator r show -q url -f host --uniq
```

{% endtab %}

{% tab title="Riskiest vulns first" %}

```bash
secator r show -q vulnerability --sort -cvss_score -l 20
```

{% endtab %}

{% tab title="Grouped vulnerabilities" %}
Show each vulnerability once, with all the targets it was matched at:

```bash
secator r show -q vulnerability --group
```

{% endtab %}
{% endtabs %}

The default `--group` fields are:

| Output type     | Grouped by         | Values listed |
| --------------- | ------------------ | ------------- |
| `vulnerability` | `name`             | `matched_at`  |
| `exploit`       | `name`             | `matched_at`  |
| `port`          | `host`             | `port`        |
| `url`           | `host`             | `url`         |
| `subdomain`     | `domain`           | `host`        |
| `technology`    | `product`          | `match`       |
| `tag`           | `category`, `name` | `match`       |

`--group` can be combined with `--format`, `--count` and `--sort`. For example, `-q vulnerability --group -f severity --count` counts **unique** vulnerabilities by severity.

### Exporting

Use `-o` / `--output` to export the results with one or more [Exporters](/in-depth/concepts/exporters), and `-of` / `--output-folder` to choose where the files go (the current directory by default):

```bash
secator r show scans/5 -o json,csv -of ./exports
```

### Deduplication

`--dedupe` removes duplicate findings, e.g. the same URL found by several tools or runs. `--no-dedupe` keeps them. When neither is passed, `secator` uses the `runners.remove_duplicates` setting:

```bash
secator r show -w mydomain --dedupe
secator config set runners.remove_duplicates true   # dedupe by default
```

### Time filter

Use `-d` / `--time-delta` to keep only recent findings:

```bash
secator r show -w mydomain -q vulnerability -d 1d
```

***

## Querying with `secator query`

`secator query` (or `secator q`) is the quickest way to search for findings. It accepts the same options as `secator r show`, but takes the filter expression as its argument:

```bash
secator q "vulnerability.severity == 'critical'"
secator q url -f "{url} {status_code}"
secator q "port.state == 'open'" -w mydomain
```

To scope a query to specific runs, use `-rf` / `--report-filter`. It works like the `REPORT_QUERY` argument of `r show`:

```bash
secator q "port.state == 'open'" -rf scans/23,tasks/10
```

With no argument, `secator q` returns all results, filtered by the options you pass:

```bash
secator q -rf scans/23 -o json
```

### How the argument is read

`secator q <ARG>` reads its argument in this order:

1. **Saved query.** If `ARG` is the name of a saved query, its expression is used (see [#saved-queries](#saved-queries "mention")).
2. **Filter expression.** If `ARG` looks like an expression (it contains an operator such as `==`, `<`, `~=`, `&&`, `||` or `in [`, is a dotted field path like `ip.alive`, or is an output type name like `url`), it is run as a query.
3. **Natural language.** Otherwise, the question is sent to the `ai` task in chat mode, run against the selected workspace:

```bash
secator q "Which hosts expose an admin panel?"
# same as: secator x ai --mode chat -p "Which hosts expose an admin panel?"
```

{% hint style="info" %}
The natural-language mode needs the `ai` task to be set up. Only `-w` / `--workspace` is used in this mode.
{% endhint %}

### Saved queries

Save a query under a name with `--save`, then run it by name:

```bash
secator q "vulnerability.severity in [critical, high] && vulnerability.confidence == 'high'" --save vuln_high
secator q vuln_high -f "{vulnerability.matched_at}" -w mydomain
```

Saved queries are stored in the `queries` section of your config, so you can also manage them with `secator config`:

```bash
secator config set queries.kev "vulnerability.tags ~= 'kev'"   # save a query
secator config get queries                                      # list saved queries
```

***

## Query syntax

`-q` (for `r show`) and the argument of `secator q` use the same expression syntax.

### Selecting a type

Use an output type name on its own to select all findings of that type:

```bash
secator q vulnerability
secator q url
```

See [Output types](/in-depth/concepts/output-types) for the list of types. To filter on a field, use `type.field`:

```bash
secator q "url.status_code == 200"
```

### Operators

| Operator             | Meaning                                | Example                                     |
| -------------------- | -------------------------------------- | ------------------------------------------- |
| `==`                 | Equals                                 | `port.port == 443`                          |
| `!=`                 | Not equal                              | `url.status_code != 404`                    |
| `>`, `>=`, `<`, `<=` | Numeric comparison                     | `vulnerability.cvss_score >= 7`             |
| `~=`                 | Matches regex (case-insensitive)       | `url.url ~= 'admin'`                        |
| `!~=`                | Doesn't match regex (case-insensitive) | `subdomain.host !~= '^dev'`                 |
| `in [...]`           | Value is in list                       | `port.port in [22, 80, 443]`                |
| `not in [...]`       | Value is not in list                   | `vulnerability.severity not in [info, low]` |
| `type.field`         | Field is truthy                        | `ip.alive`                                  |
| `not type.field`     | Field is not `True`                    | `not url.verified`                          |

Values can be quoted or not. `true` / `false` become booleans, and numbers become numbers. Nested fields use dots, e.g. `vulnerability.extra_data.template_id ~= 'cve'`.

`!=`, `!~=` and `not in` only match findings that **have** the field: `vulnerability.severity != 'info'` doesn't return ports or URLs.

### Combining conditions

Combine conditions with `&&` / `and` (all must match) or `||` / `or` (any can match):

```bash
secator q "vulnerability.severity == 'critical' && vulnerability.tags ~= 'exploitable'"
secator q "port.port == 22 || port.port == 3389"
```

{% hint style="warning" %}
You can't mix `&&` and `||` in the same expression.
{% endhint %}

### MongoDB JSON

You can also pass a MongoDB-style JSON filter:

```bash
secator r show -q '{"_type": "port", "port": {"$in": [80, 443]}}'
```

### Unknown fields

If a field doesn't exist on the type you query, `secator` prints a warning with the list of available fields, so you can use a typo as a quick field reference:

```bash
secator q "port.foo == 1"
# [WRN] Field 'foo' does not exist on type 'port'. Available fields: confidence, cpes, extra_data, host, ip, ...
```

{% hint style="info" %}
Findings marked as **false positives** are always hidden from query results.
{% endhint %}

### Examples

```bash
# Known-exploited (KEV) vulnerabilities with high confidence
secator q "vulnerability.tags ~= 'kev' && vulnerability.confidence == 'high'"

# Critical and high vulnerabilities (severity_nb: 0 = critical, 1 = high, ...)
secator q "vulnerability.severity_nb < 2"

# Exploits found for a given CVE
secator q "exploit.cves ~= 'CVE-2021-44228'"

# Top 15 open ports, services and technologies
secator q "port.state == 'open'" -f port --count -l 15
secator q port -f service_name --count -l 15
secator q technology -f "{product}/{version}" --count -l 15

# Live URLs, ready to pipe into another tool
secator q "url.status_code == 200" -f url --uniq | secator x nuclei
```

***

## Query backends

All `secator r` and `secator q` commands read from a **query backend**, selected with `--driver`:

| Driver    | Reads from                                           |
| --------- | ---------------------------------------------------- |
| `local`   | The JSON reports in `~/.secator/reports/` (default). |
| `mongodb` | A MongoDB database filled by the `mongodb` driver.   |
| `sqlite`  | A SQLite database filled by the `sqlite` driver.     |
| `api`     | A remote API filled by the `api` driver.             |

If `--driver` isn't set, `secator` uses the first backend listed in `drivers.defaults`, and falls back to `local`:

```bash
secator x httpx example.com -driver mongodb       # store results in MongoDB
secator q url --driver mongodb                    # query them
secator config set drivers.defaults mongodb       # ... or use MongoDB by default
```

See [Drivers](/in-depth/concepts/drivers) to set drivers up.

***

## Deleting reports

`secator report delete` (or `secator r rm`) deletes one or more reports. It accepts space- or comma-separated runner paths, and numeric ranges:

```bash
secator r delete tasks/23 tasks/24 workflows/21
secator r delete tasks/23,workflows/21
secator r delete tasks/136-140,workflows/10-21 -w mydomain
```

The command lists what will be deleted and asks for confirmation. Pass `-y` / `--yes` to skip the prompt.

With the `local` driver, the report folders are removed. With the `mongodb`, `sqlite` or `api` drivers, the runner and its findings are deleted from the backend.

To delete a whole workspace and all its reports, use `secator ws rm` (see [Workspaces](/in-depth/concepts/workspaces)).


# Examples

... or concrete use cases for secator.

***

### **Find subdomains using `subfinder` and run HTTP probes using `httpx`**

{% tabs %}
{% tab title="CLI" %}

```bash
secator x subfinder -raw alibaba.com | secator x httpx -rl 10 -ss
```

{% endtab %}

{% tab title="Python" %}

```python
from secator.tasks import subfinder, httpx

target = 'alibaba.com'
results = subfinder(target).run()
hosts = [_.host for _ in results if _._type == 'subdomain']
for probe in httpx(hosts, rate_limit=10, screenshot=True):
    print('Found alive subdomain URL {url}[{status_code}]'.format(**probe))
```

{% endtab %}
{% endtabs %}

***

### **Run host reconnaissance workflow**

{% tabs %}
{% tab title="CLI" %}

```bash
secator w host_recon cnn.com
```

{% endtab %}

{% tab title="Python" %}

```python
from secator.workflows import host_recon

target = 'cnn.com'
for result in host_recon(target):  # consume results live
    print(result)
```

{% endtab %}
{% endtabs %}

***

### **Fuzz URLs with multiple fuzzers and a custom wordlist**

{% tabs %}
{% tab title="CLI" %}

```bash
secator w url_fuzz example.com -mc 200,302 -rl 1 -w dicc.txt -o table
```

{% endtab %}

{% tab title="Python" %}

```python
from secator.workflows import url_fuzz

target = 'example.com'
opts = {
    'match_codes': '200, 302',
    'rate_limit': 1 # req/s
    'quiet': True,
    'ffuf.wordlist': 'dicc.txt' # ffuf wordlist
}

# Print results live and a summary table at the end of the run
for result in url_fuzz(target, exporters=['table']):
    print(result)
```

{% endtab %}
{% endtabs %}

***


# 5 minutes secator session

or how you can hack faster than ever before...

This quickstart will be focused on using `secator` to find vulnerabilities on the website <http://testphp.vulnweb.com>.

We will start by using simple `secator` tasks, and then show how to use workflows to considerably speed up the session.

***

## Using tasks

<details>

<summary>Step 1: Run <code>katana</code> on the root URL</summary>

We'll start by using a crawler to find some URLs that could be interesting to exploit for vulnerabiliites. We most often use `katana`, a tool by ProjectDiscovery.

We'll save our results to a `.txt` file:

<pre class="language-bash"><code class="lang-bash"><strong>secator x katana http://testphp.vulnweb.com -o txt
</strong>                         __            
   ________  _________ _/ /_____  _____
  / ___/ _ \/ ___/ __ `/ __/ __ \/ ___/
 (__  /  __/ /__/ /_/ / /_/ /_/ / /    
/____/\___/\___/\__,_/\__/\____/_/     v0.0.1

                    freelabz.com

katana -silent -jc -js-crawl -known-files all -u http://testphp.vulnweb.com -json -concurrency 50                                                                                                                                _base.py:614
🔗 http://testphp.vulnweb.com [200] [Nginx:1.19.0, PHP:5.6.40, Ubuntu, DreamWeaver]
🔗 http://testphp.vulnweb.com/high [404] [Nginx:1.19.0]
🔗 http://testphp.vulnweb.com/index.php
🔗 http://testphp.vulnweb.com/style.css [200] [Nginx:1.19.0]
🔗 http://testphp.vulnweb.com/privacy.php [404] [PHP:5.6.40, Ubuntu, Nginx:1.19.0]
🔗 http://testphp.vulnweb.com/AJAX/index.php [200] [Nginx:1.19.0, PHP:5.6.40, Ubuntu]
🔗 http://testphp.vulnweb.com/categories.php [200] [Nginx:1.19.0, PHP:5.6.40, Ubuntu, DreamWeaver]
🔗 http://testphp.vulnweb.com/cart.php [200] [PHP:5.6.40, Ubuntu, DreamWeaver, Nginx:1.19.0]
🔗 http://testphp.vulnweb.com/artists.php [200] [DreamWeaver, Nginx:1.19.0, PHP:5.6.40, Ubuntu]
🔗 http://testphp.vulnweb.com/Mod_Rewrite_Shop/ [200] [Nginx:1.19.0, PHP:5.6.40, Ubuntu]
🔗 http://testphp.vulnweb.com/hpp/ [200] [Nginx:1.19.0, PHP:5.6.40, Ubuntu]
🔗 http://testphp.vulnweb.com/disclaimer.php [200] [DreamWeaver, Nginx:1.19.0, PHP:5.6.40, Ubuntu]
🔗 http://testphp.vulnweb.com/login.php [200] [DreamWeaver, Nginx:1.19.0, PHP:5.6.40, Ubuntu]
🔗 http://testphp.vulnweb.com/Templates/main_dynamic_template.dwt.php [200] [Ubuntu, Nginx:1.19.0, PHP:5.6.40]
🔗 http://testphp.vulnweb.com/guestbook.php [200] [Nginx:1.19.0, PHP:5.6.40, Ubuntu, DreamWeaver]
🔗 http://testphp.vulnweb.com/userinfo.php
🔗 http://testphp.vulnweb.com/Mod_Rewrite_Shop/Details/color-printer/3/ [200] [Nginx:1.19.0, PHP:5.6.40, Ubuntu]
🔗 http://testphp.vulnweb.com/comment.php?aid=3 [200] [Ubuntu, Nginx:1.19.0, PHP:5.6.40]
🔗 http://testphp.vulnweb.com/search.php?test=query [200] [DreamWeaver, Nginx:1.19.0, PHP:5.6.40, Ubuntu]
🔗 http://testphp.vulnweb.com/Mod_Rewrite_Shop/Details/network-attached-storage-dlink/1/ [200] [Nginx:1.19.0, PHP:5.6.40, Ubuntu]
🔗 http://testphp.vulnweb.com/Mod_Rewrite_Shop/Details/web-camera-a4tech/2/ [200] [Nginx:1.19.0, PHP:5.6.40, Ubuntu]
🔗 http://testphp.vulnweb.com/Templates/high
🔗 http://testphp.vulnweb.com/comment.php?aid=2
🔗 http://testphp.vulnweb.com/artists.php?artist=1 [200] [DreamWeaver, Nginx:1.19.0, PHP:5.6.40, Ubuntu]
🔗 http://testphp.vulnweb.com/hpp/?pp=12 [200] [Nginx:1.19.0, PHP:5.6.40, Ubuntu]
🔗 http://testphp.vulnweb.com/signup.php [200] [Nginx:1.19.0, PHP:5.6.40, Ubuntu, DreamWeaver]
🔗 http://testphp.vulnweb.com/comment.php?aid=1
🔗 http://testphp.vulnweb.com/AJAX/showxml.php [200] [Nginx:1.19.0, PHP:5.6.40, Ubuntu]
🔗 http://testphp.vulnweb.com/AJAX/styles.css [200] [Nginx:1.19.0]
🔗 http://testphp.vulnweb.com/artists.php?artist=3 [200] [PHP:5.6.40, Ubuntu, DreamWeaver, Nginx:1.19.0]
🔗 http://testphp.vulnweb.com/listproducts.php?cat=3 [200] [Nginx:1.19.0, PHP:5.6.40, Ubuntu, DreamWeaver]
🔗 http://testphp.vulnweb.com/listproducts.php?cat=2 [200] [Nginx:1.19.0, PHP:5.6.40, Ubuntu, DreamWeaver]
🔗 http://testphp.vulnweb.com/showimage.php?file= [200] [Nginx:1.19.0, PHP:5.6.40, Ubuntu]
🔗 http://testphp.vulnweb.com/artists.php?artist=2 [200] [Nginx:1.19.0, PHP:5.6.40, Ubuntu, DreamWeaver]
🔗 http://testphp.vulnweb.com/listproducts.php?cat=4
🔗 http://testphp.vulnweb.com/listproducts.php?cat=1 [200] [Nginx:1.19.0, PHP:5.6.40, Ubuntu, DreamWeaver]
🗄 Saved TXT reports to 
   • ~/.secator/reports/default/tasks/0/report_url.txt
</code></pre>

`katana` found some pretty interesting results, including some PHP files that could potentially be vulnerable.

</details>

<details>

<summary>Step 2: Run <code>httpx</code> on found URLs to alive URLs</summary>

Crawlers usually find URLs from HTML response bodies, which means we have no ideas if those URLs will actually respond to HTTP requests or not.

In order to filter only the URLs that will give a valid HTTP status code, we can use `httpx` on the previous results (txt file).\
\
We'll add some rate limiting (`-rl`) in order to respect the server and not DDoS it for no reason.

We want to keep only some HTTP codes (`-mc`) for instance `200`, `301`, and `500` in case we have there are errors we can take advantage of:

```bash
secator x httpx ~/.secator/reports/default/tasks/0/report_url.txt -rl 10 -mc 200,301,500

                         __            
   ________  _________ _/ /_____  _____
  / ___/ _ \/ ___/ __ `/ __/ __ \/ ___/
 (__  /  __/ /__/ /_/ / /_/ /_/ / /    
/____/\___/\___/\__,_/\__/\____/_/     v0.0.1

                    freelabz.com

httpx -silent -td -asn -cdn -l /tmp/httpx_2023_07_04-01_19_35_686343_PM.txt -json -threads 50 -match-code 200,301,500                                                                                                            _base.py:614
🔗 http://testphp.vulnweb.com/showimage.php?file [200] [nginx/1.19.0] [Nginx:1.19.0, PHP:5.6.40, Ubuntu] [image/jpeg] [196]
🔗 http://testphp.vulnweb.com/hpp [200] [HTTP Parameter Pollution Example] [nginx/1.19.0] [Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [203]
🔗 http://testphp.vulnweb.com/cart.php [200] [you cart] [nginx/1.19.0] [DreamWeaver, Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [4903]
🔗 http://testphp.vulnweb.com/categories.php [200] [picture categories] [nginx/1.19.0] [DreamWeaver, Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [6115]
🔗 http://testphp.vulnweb.com/artists.php?artist=3 [200] [artists] [nginx/1.19.0] [DreamWeaver, Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [6193]
🔗 http://testphp.vulnweb.com/comment.php?aid=3 [200] [comment on artist] [nginx/1.19.0] [Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [1252]
🔗 http://testphp.vulnweb.com/comment.php?aid=2 [200] [comment on artist] [nginx/1.19.0] [Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [1252]
🔗 http://testphp.vulnweb.com/AJAX/showxml.php [200] [nginx/1.19.0] [Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [11]
🔗 http://testphp.vulnweb.com/style.css [200] [nginx/1.19.0] [Nginx:1.19.0] [text/css] [5482]
🔗 http://testphp.vulnweb.com/artists.php?artist=1 [200] [artists] [nginx/1.19.0] [DreamWeaver, Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [6251]
🔗 http://testphp.vulnweb.com/Templates/main_dynamic_template.dwt.php [200] [Document titleg] [nginx/1.19.0] [Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [4697]
🔗 http://testphp.vulnweb.com/Mod_Rewrite_Shop/Details/color-printer/3 [200] [nginx/1.19.0] [Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [313]
🔗 http://testphp.vulnweb.com/AJAX/styles.css [200] [nginx/1.19.0] [Nginx:1.19.0] [text/css] [562]
🔗 http://testphp.vulnweb.com/hpp/?pp=12 [200] [HTTP Parameter Pollution Example] [nginx/1.19.0] [Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [383]
🔗 http://testphp.vulnweb.com/login.php [200] [login page] [nginx/1.19.0] [DreamWeaver, Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [5523]
🔗 http://testphp.vulnweb.com/guestbook.php [200] [guestbook] [nginx/1.19.0] [DreamWeaver, Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [5390]
🔗 http://testphp.vulnweb.com/signup.php [200] [signup] [nginx/1.19.0] [DreamWeaver, Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [6033]
🔗 http://testphp.vulnweb.com/Mod_Rewrite_Shop/Details/network-attached-storage-dlink/1 [200] [nginx/1.19.0] [Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [319]
🔗 http://testphp.vulnweb.com/search.php?test=query [200] [search] [nginx/1.19.0] [DreamWeaver, Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [4732]
🔗 http://testphp.vulnweb.com/listproducts.php?cat=3 [200] [pictures] [nginx/1.19.0] [DreamWeaver, Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [4699]
🔗 http://testphp.vulnweb.com/AJAX/index.php [200] [ajax test] [nginx/1.19.0] [Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [4236]
🔗 http://testphp.vulnweb.com/disclaimer.php [200] [disclaimer] [nginx/1.19.0] [DreamWeaver, Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [5524]
🔗 http://testphp.vulnweb.com/artists.php?artist=2 [200] [artists] [nginx/1.19.0] [DreamWeaver, Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [6193]
🔗 http://testphp.vulnweb.com/listproducts.php?cat=4 [200] [pictures] [nginx/1.19.0] [DreamWeaver, Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [4699]
🔗 http://testphp.vulnweb.com/artists.php [200] [artists] [nginx/1.19.0] [DreamWeaver, Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [5328]
🔗 http://testphp.vulnweb.com/listproducts.php?cat=2 [200] [pictures] [nginx/1.19.0] [DreamWeaver, Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [5311]
🔗 http://testphp.vulnweb.com/Mod_Rewrite_Shop [200] [nginx/1.19.0] [Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [975]
🔗 http://testphp.vulnweb.com/listproducts.php?cat=1 [200] [pictures] [nginx/1.19.0] [DreamWeaver, Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [7880]
🔗 http://testphp.vulnweb.com/comment.php?aid=1 [200] [comment on artist] [nginx/1.19.0] [Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [1252]
🔗 http://testphp.vulnweb.com/Mod_Rewrite_Shop/Details/web-camera-a4tech/2 [200] [nginx/1.19.0] [Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [279]
🔗 http://testphp.vulnweb.com [200] [Home of Acunetix Art] [nginx/1.19.0] [DreamWeaver, Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [4958]
🔗 http://testphp.vulnweb.com/index.php [200] [Home of Acunetix Art] [nginx/1.19.0] [DreamWeaver, Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [4958]
🗄 Saved TXT reports to 
   • ~/.secator/reports/default/tasks/1/report_url.txt
```

</details>

<details>

<summary>Step 3: Run <code>gf</code> on found URLs</summary>

`gf` allows to run patterns on URLs, we can use it to quickly detect potential interesting URLs including XSS, LFIs, SSRFs, RCEs, Interesting params, or Insecure Direct Object references.\
Let's hunt for potential XSS:

<pre><code>secator x gf --pattern xss ~/.secator/reports/default/tasks/1/report_url.txt
<strong>🏷️ [xss] http://testphp.vulnweb.com/comment.php?aid=1 []
</strong>🏷️ [xss] http://testphp.vulnweb.com/comment.php?aid=2 []
🏷️ [xss] http://testphp.vulnweb.com/comment.php?aid=3 []
🏷️ [xss] http://testphp.vulnweb.com/hpp/?pp=12 []
</code></pre>

Mmmh, it seems like we might have some interesting XSS targets here. Let's use `dalfox` to find out !

</details>

<details>

<summary>Step 4: Run <code>dalfox</code> on potential XSS links</summary>

`dalfox` is a pretty thorough XSS checker, let's run it on the targets we've identified:

```
secator x dalfox http://testphp.vulnweb.com/hpp/?pp=12

                         __            
   ________  _________ _/ /_____  _____
  / ___/ _ \/ ___/ __ `/ __/ __ \/ ___/
 (__  /  __/ /__/ /_/ / /_/ /_/ / /    
/____/\___/\___/\__,_/\__/\____/_/     v0.0.1

                    freelabz.com

dalfox --silence url 'http://testphp.vulnweb.com/hpp/?pp=12' --format json --worker 50                                                                                                                                           _base.py:614
🚨 [Verified XSS] [high] http://testphp.vulnweb.com/hpp/ [CWE-83] [inject_type:inATTR-double(3)-URL, poc_type:plain, method:GET, 
data:http://testphp.vulnweb.com/hpp/?pp=12%22id%3Dx+tabindex%3D1+style%3D%22display%3Ablock%3Btransition%3Aoutline+1s%3B%22+ontransitionend%3Dalert.apply%28null%2C1%29+class%3Ddalfox+, param:pp, payload:"id=x tabindex=1 
style="display:block;transition:outline 1s;" ontransitionend=alert.apply(null,1) class=dalfox , evidence:4 line:  ms.php?p=valid&pp=12"id=x tabindex=1 style="display:block;transition:outline 1s;, message_id:1103, message_str:Triggered XSS Payload 
(found DOM Object): pp="id=x tabindex=1 style="display:block;transition:outline 1s;" ontransitionend=alert.apply(null,1) class=dalfox ]
```

**We have found a Verified XSS !**

We could verify this XSS in our browser to validate it, but `dalfox` already gave us evidence that it works...

</details>

***

## Using a task pipe

`secator` supports UNIX pipes out-of-the-box. You can write a `secator` pipe that can automate the 4 previous steps:

```bash
secator x katana http://testphp.vulnweb.com | secator x httpx | secator x gf --pattern lfi | secator x dalfox
```

You don't have to specify any additional flag than when running normally, since `secator` will detect that you are running a UNIX pipe and automagically pass the proper inputs between task invocations:

* By default, it will pass on `stdin` the raw string results from the previous task. If the task can output multiple [Output types](/in-depth/concepts/output-types), then the first one in the class definition `output_types` attribute is picked.
* We can specify which fields we want to use when passing raw strings using the `-fmt` option.

<details>

<summary>Command output</summary>

```
katana -silent -jc -js-crawl -known-files all -u http://testphp.vulnweb.com -json -concurrency 50                                                                                                                                _base.py:614
...
httpx -silent -td -asn -cdn -l /tmp/httpx_2023_07_04-01_50_43_044323_PM.txt -json -threads 50                                                                                                                                    _base.py:614
...
cat /tmp/gf_2023_07_04-01_50_49_787397_PM.txt | gf xss                                                                                                                                                                           _base.py:614
dalfox --silence file /tmp/dalfox_2023_07_04-01_50_49_855768_PM.txt --format json --worker 50                                                                                                                                    _base.py:614
🚨 [Verified XSS] [high] http://testphp.vulnweb.com/hpp/ [CWE-83] [inject_type:inATTR-double(3)-URL, poc_type:plain, method:GET, 
data:http://testphp.vulnweb.com/hpp/?pp=12%22%26%2339%3B%3E%3Caudio+controls+ondurationchange%3Dprompt%281%29+id%3Ddalfox%3E%3Csource+src%3D1.mp3+type%3Daudio%2Fmpeg%3E%3C%2Faudio%3E, param:pp, payload:"&#39;><audio controls 
ondurationchange=prompt(1) id=dalfox><source src=1.mp3 type=audio/mpeg></audio>, evidence:4 line:  ms.php?p=valid&pp=12"&#39;><audio controls ondurationchange=prompt(1) id=dalfox>, message_id:1862, message_str:Triggered XSS Payload (found DOM 
Object): pp="&#39;><audio controls ondurationchange=prompt(1) id=dalfox><source src=1.mp3 type=audio/mpeg></audio>]
```

</details>

This is already a good time improvement over the previous lengthy set of tasks.

***

## Using a workflow

Task pipes are good to quickly find things, but workflows are much better to repeat the same set of tasks over separate sets of targets, with the same input options for all tasks, while filtering final results and reacting to live results, etc...

Here is a `secator` workflow corresponding to the previous set of tasks:

{% code title="\~/.secator/templates/xss\_finder.yaml" %}

```yaml
type: workflow
name: xss_finder
description: XSS Finder
tasks:
  katana:
    description: Crawling root URL
  httpx:
    description: Finding alive URLs
    targets_:
      - type: url
        field: url
        condition: item.status_code == 0
  gf:
    description: Identifying XSS
    pattern: xss
    targets_:
      - url.url
  dalfox:
    description: Verifying XSS
    targets_:
      - type: tag
        field: match
        condition: item.name == 'xss'
```

{% endcode %}

And here is the workflow run using `secator`:

```
secator w xss_finder.yaml http://testphp.vulnweb.com
```

<details>

<summary>Command output</summary>

```
                         __            
   ________  _________ _/ /_____  _____
  / ___/ _ \/ ___/ __ `/ __/ __ \/ ___/
 (__  /  __/ /__/ /_/ / /_/ /_/ / /    
/____/\___/\___/\__,_/\__/\____/_/     v0.0.1

                    freelabz.com                                                                                                                                                                                               _base.py:614

🔧 Crawling root URL ...
katana -silent -jc -js-crawl -known-files all -u http://testphp.vulnweb.com -json -concurrency 50                                                                                                                                _base.py:614
🔗 http://testphp.vulnweb.com [200] [Nginx:1.19.0, Ubuntu, PHP:5.6.40, DreamWeaver]
🔗 http://testphp.vulnweb.com/high [404] [Nginx:1.19.0]
🔗 http://testphp.vulnweb.com/Mod_Rewrite_Shop/ [200] [Nginx:1.19.0, Ubuntu, PHP:5.6.40]
🔗 http://testphp.vulnweb.com/index.php
🔗 http://testphp.vulnweb.com/style.css [200] [Nginx:1.19.0]
🔗 http://testphp.vulnweb.com/guestbook.php [200] [Nginx:1.19.0, Ubuntu, PHP:5.6.40, DreamWeaver]
🔗 http://testphp.vulnweb.com/Templates/main_dynamic_template.dwt.php [200] [PHP:5.6.40, Nginx:1.19.0, Ubuntu]
🔗 http://testphp.vulnweb.com/AJAX/index.php [200] [PHP:5.6.40, Nginx:1.19.0, Ubuntu]
🔗 http://testphp.vulnweb.com/login.php [200] [Nginx:1.19.0, Ubuntu, PHP:5.6.40, DreamWeaver]
🔗 http://testphp.vulnweb.com/cart.php [200] [PHP:5.6.40, Nginx:1.19.0, Ubuntu, DreamWeaver]
🔗 http://testphp.vulnweb.com/privacy.php [404] [Nginx:1.19.0, Ubuntu, PHP:5.6.40]
🔗 http://testphp.vulnweb.com/hpp/ [200] [Nginx:1.19.0, Ubuntu, PHP:5.6.40]
🔗 http://testphp.vulnweb.com/categories.php [200] [Nginx:1.19.0, Ubuntu, PHP:5.6.40, DreamWeaver]
🔗 http://testphp.vulnweb.com/artists.php [200] [Nginx:1.19.0, Ubuntu, PHP:5.6.40, DreamWeaver]
🔗 http://testphp.vulnweb.com/disclaimer.php [200] [Nginx:1.19.0, Ubuntu, PHP:5.6.40, DreamWeaver]
🔗 http://testphp.vulnweb.com/userinfo.php
🔗 http://testphp.vulnweb.com/listproducts.php?cat=3
🔗 http://testphp.vulnweb.com/comment.php?aid=1 [200] [Nginx:1.19.0, Ubuntu, PHP:5.6.40]
🔗 http://testphp.vulnweb.com/listproducts.php?cat=4 [200] [Nginx:1.19.0, Ubuntu, PHP:5.6.40, DreamWeaver]
🔗 http://testphp.vulnweb.com/comment.php?aid=3
🔗 http://testphp.vulnweb.com/artists.php?artist=2 [200] [Nginx:1.19.0, Ubuntu, PHP:5.6.40, DreamWeaver]
🔗 http://testphp.vulnweb.com/comment.php?aid=2
🔗 http://testphp.vulnweb.com/signup.php [200] [PHP:5.6.40, DreamWeaver, Nginx:1.19.0, Ubuntu]
🔗 http://testphp.vulnweb.com/artists.php?artist=3 [200] [Nginx:1.19.0, Ubuntu, PHP:5.6.40, DreamWeaver]
🔗 http://testphp.vulnweb.com/artists.php?artist=1 [200] [Ubuntu, PHP:5.6.40, DreamWeaver, Nginx:1.19.0]
🔗 http://testphp.vulnweb.com/listproducts.php?cat=1 [200] [Nginx:1.19.0, Ubuntu, PHP:5.6.40, DreamWeaver]
🔗 http://testphp.vulnweb.com/AJAX/showxml.php [200] [Nginx:1.19.0, Ubuntu, PHP:5.6.40]
🔗 http://testphp.vulnweb.com/showimage.php?file= [200] [Nginx:1.19.0, Ubuntu, PHP:5.6.40]
🔗 http://testphp.vulnweb.com/hpp/?pp=12 [200] [Ubuntu, PHP:5.6.40, Nginx:1.19.0]
🔗 http://testphp.vulnweb.com/AJAX/styles.css [200] [Nginx:1.19.0]
🔗 http://testphp.vulnweb.com/Templates/high
🔗 http://testphp.vulnweb.com/Mod_Rewrite_Shop/Details/network-attached-storage-dlink/1/ [200] [PHP:5.6.40, Nginx:1.19.0, Ubuntu]
🔗 http://testphp.vulnweb.com/Mod_Rewrite_Shop/Details/web-camera-a4tech/2/ [200] [PHP:5.6.40, Nginx:1.19.0, Ubuntu]
🔗 http://testphp.vulnweb.com/listproducts.php?cat=2 [200] [PHP:5.6.40, Nginx:1.19.0, Ubuntu, DreamWeaver]
🔗 http://testphp.vulnweb.com/Mod_Rewrite_Shop/Details/color-printer/3/ [200] [PHP:5.6.40, Nginx:1.19.0, Ubuntu]
🔗 http://testphp.vulnweb.com/search.php?test=query [200] [Nginx:1.19.0, Ubuntu, DreamWeaver, PHP:5.6.40]

🔧 Finding alive URLs ...
httpx -silent -td -asn -cdn -l /tmp/httpx_2023_07_04-02_03_45_807367_PM.txt -json -threads 50                                                                                                                                    _base.py:614
🔗 http://testphp.vulnweb.com/Templates/high [404] [404 Not Found] [nginx/1.19.0] [Nginx:1.19.0] [text/html] [153]
🔗 http://testphp.vulnweb.com/index.php [200] [Home of Acunetix Art] [nginx/1.19.0] [DreamWeaver, Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [4958]
🔗 http://testphp.vulnweb.com/comment.php?aid=2 [200] [comment on artist] [nginx/1.19.0] [Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [1252]
🔗 http://testphp.vulnweb.com/userinfo.php [302] [nginx/1.19.0] [Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [14]
🔗 http://testphp.vulnweb.com/comment.php?aid=3 [200] [comment on artist] [nginx/1.19.0] [Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [1252]
🔗 http://testphp.vulnweb.com/listproducts.php?cat=3 [200] [pictures] [nginx/1.19.0] [DreamWeaver, Nginx:1.19.0, PHP:5.6.40, Ubuntu] [text/html] [4699]

🔧 Identifying XSS ...
cat /tmp/gf_2023_07_04-02_03_52_398624_PM.txt | gf xss                                                                                                                                                                           _base.py:614
🏷️ [xss] http://testphp.vulnweb.com/comment.php?aid=1 []
🏷️ [xss] http://testphp.vulnweb.com/comment.php?aid=2 []
🏷️ [xss] http://testphp.vulnweb.com/comment.php?aid=3 []
🏷️ [xss] http://testphp.vulnweb.com/hpp/?pp=12 []

🔧 Verifying XSS ...
dalfox --silence file /tmp/dalfox_2023_07_04-02_03_52_411553_PM.txt --format json --worker 50                                                                                                                                    _base.py:614
🚨 [Verified XSS] [high] http://testphp.vulnweb.com/hpp/ [CWE-83] [inject_type:inATTR-double(3)-URL, poc_type:plain, method:GET, 
data:http://testphp.vulnweb.com/hpp/?pp=12%22onmouseenter%3Dprompt.call%28null%2C1%29+class%3Ddalfox+, param:pp, payload:"onmouseenter=prompt.call(null,1) class=dalfox , evidence:4 line:  ms.php?p=valid&pp=12"onmouseenter=prompt.call(null,1) 
class=dalfox ">link2</a><b, message_id:1540, message_str:Triggered XSS Payload (found DOM Object): pp="onmouseenter=prompt.call(null,1) class=dalfox ]
                                                                                                                                                                                  _base.py:614
🗄 Saved JSON report to /home/vagrant/.secator/reports/default/workflows/workflow_xss_finder_2023_07_04-02_04_20_160856_PM.json
🗄 Saved CSV reports to 
   • /home/vagrant/.secator/reports/default/workflows/workflow_xss_finder_target_2023_07_04-02_04_20_160856_PM.csv
   • /home/vagrant/.secator/reports/default/workflows/workflow_xss_finder_url_2023_07_04-02_04_20_160856_PM.csv
   • /home/vagrant/.secator/reports/default/workflows/workflow_xss_finder_tag_2023_07_04-02_04_20_160856_PM.csv
   • /home/vagrant/.secator/reports/default/workflows/workflow_xss_finder_vulnerability_2023_07_04-02_04_20_160856_PM.csv

```

</details>

***


# Global options

... or options that you can use in any context.

**Global options** apply to all runners (task, workflow, scan) and allow to control the overrall behaviour of the run.

***

### Workspace (`-ws`)

You can pass a workspace name to use for the runner, which will save all reports to a subfolder named after the workspace.

<details>

<summary><strong>Example: Save results to <code>mydomain</code> workspace</strong></summary>

```bash
secator x httpx mydomain.com -ws mydomain
secator w host_recon mydomain.com -ws mydomain
secator s domain mydomain.com -ws mydomain
```

</details>

***

### Output (`-o`)

You can export reports in various formats using built-in exporters.

<details>

<summary><strong>Example:</strong> export reports as <code>table</code>, <code>csv</code>, and <code>json</code>formats</summary>

```bash
secator x httpx mydomain.com -o table,csv,json
secator w host_recon mydomain.com -o table,csv,json
secator s domain mydomain.com -o table,csv,json
```

</details>

{% hint style="info" %}
Learn more about [Exporters](/in-depth/concepts/exporters).
{% endhint %}

***

### Drivers (`-driver`)

You can export live results to different targets using drivers.

To use drivers, make sure you install the corresponding addon using `secator install addons <NAME>`.

<details>

<summary><strong>Example -</strong> export live results to MongoDB</summary>

First, install the `mongodb` addon using `secator install addons mongodb`

Then, use the `-driver` flag route your results:

```bash
secator x httpx mydomain.com -driver mongodb
secator w host_recon mydomain.com -driver mongodb
secator s domain mydomain.com -driver mongodb
```

</details>

{% hint style="info" %}
Learn more about [Drivers](/in-depth/concepts/drivers).
{% endhint %}

***

### Profiles (`-pf`)

You can apply one or more [Profiles](/in-depth/concepts/profiles) (comma-separated) to set many options at once.

<details>

<summary><strong>Example -</strong> run a polite, passive domain scan</summary>

```bash
secator s domain mydomain.com -pf polite,passive
```

</details>

***

### Sync / worker mode (`-sync`, `-np`)

When a Celery worker is configured, runs are sent to workers by default. Use `-sync` to run locally instead, or `-np` (`--no-poll`) to send the run to workers without waiting for its results.

<details>

<summary><strong>Example -</strong> send a scan to workers and return immediately</summary>

```bash
secator s host mydomain.com -np
```

</details>

{% hint style="info" %}
Learn more about [Distributed runs with Celery](/in-depth/distributed-runs-with-celery).
{% endhint %}

***

### Inspect a runner (`-dry`, `-tree`, `-yaml`)

Show what a runner would do without running it:

```bash
secator x nmap mydomain.com -dry   # show the exact command that would run
secator s host -tree               # show the scan tree (workflows and tasks)
secator w host_recon -yaml         # show the workflow YAML
```

***

### Runtime stats and profiling (`-stat`, `-pyinstrument`, `-memray`)

`-stat` prints runtime statistics (CPU, memory) of the commands being run. `-pyinstrument` and `-memray` profile `secator` itself (install the `trace` addon first); results are saved to `~/.secator/performance`.

```bash
secator x httpx mydomain.com -stat
```

***

### Target scope (Python only)

Runners accept `in_scope` and `out_of_scope` options: allow / deny lists checked against the inputs of every runner, including targets discovered during a workflow or scan. Out-of-scope inputs are dropped with a warning. Entries can be IPs, CIDR ranges, exact hosts, wildcards (`*.example.com` matches sub-domains, not the apex) or regexes. Deny wins over allow, and an empty `in_scope` allows everything not denied. Non-network inputs (emails, usernames, paths) are never filtered.

```python
from secator.workflows import host_recon

workflow = host_recon(
    'mydomain.com',
    in_scope=['mydomain.com', '*.mydomain.com', '10.0.0.0/24'],
    out_of_scope=['admin.mydomain.com'],
)
workflow.run()
```

***


# Meta options

... or options that are mutualized among task categories for efficiency, speed, and user-friendliness.

**Meta options** apply to **tasks**, **workflows**, or **scans**. When passed to **workflows** or **scans**, they will be passed to each task contained in the runner.

{% hint style="warning" %}
Some tasks, workflows, or scans do not support some of the options mentioned below. Ru&#x6E;**`secator x/w/s <name> --help`** to get the complete list of supported options.
{% endhint %}

***

## Execution Options

### Threads (`-threads`)

Number of threads to use. Applies to all tasks supporting threads (or concurrency).

<details>

<summary>Example: set 50 threads</summary>

```bash
secator w host_recon mydomain.com -threads 50
```

</details>

***

## Requests Options

The following options will apply to tasks making network requests (if they implement it), no matter the protocol used (HTTP, TCP, UDP, DNS, FTP, ...).

### Proxy (`-proxy`)

Proxy (HTTP, Socks5, ...) to use when communicating with the targets.

<details>

<summary>Example: set proxies in config and <code>-proxy</code> to <code>auto</code></summary>

```bash
secator config set http.http_proxy http://localhost:8080
secator config set http.socks5_proxy socks5://localhost:9050
secator w host_recon mydomain.com -proxy auto  # auto choose the right proxy
```

</details>

{% hint style="info" %}
Learn more about [Proxies](/in-depth/concepts/proxies).
{% endhint %}

***

### Rate limit (`-rl`)

Rate limit is an upper limit on the number of requests per second.

<details>

<summary>Example: set a rate limit of <code>50</code> requests/second</summary>

```bash
secator w host_recon mydomain.com -rl 50
```

</details>

***

### Timeout (`-timeout`)

Timeout is the time to wait (in seconds) before giving up on the request.

<details>

<summary>Example: set a request timeout of <code>10</code> seconds</summary>

```bash
secator w host_recon mydomain.com -timeout 10
```

</details>

***

### Retries (`-retries`)

Number of retries for failed requests.

<details>

<summary>Example: set <code>5</code> retries for all requests</summary>

```bash
secator w host_recon mydomain.com -retries 5
```

</details>

***

### Delay (`-d`)

Delay to add between each request (in seconds).

<details>

<summary>Example: add a <code>0.5</code> second delay between requests</summary>

```bash
secator w host_recon mydomain.com -d 0.5
```

</details>

***

## HTTP Options

The following options will apply to tasks making HTTP requests (if they implement it).

### Header (`-H`)

Custom header to add to each request in the form "KEY1:VALUE1;; KEY2:VALUE2".

<details>

<summary>Example: set an <code>Authorization</code> and an <code>Accept</code> header</summary>

```bash
secator x cariddi mydomain.com -H "Authorization: Basic <TOKEN>;; Accept: application/json"
```

</details>

***

### Raw request (`-rf`)

Path to a file containing a raw HTTP request (Burp-style format). The request method, headers and body are applied to the task (unless set with `-X`, `-data`), and its URL is used as input when no target is passed.

<details>

<summary>Example: fuzz with a request saved from Burp</summary>

```bash
secator x httpx -rf request.txt                          # target taken from the request
secator x ffuf http://mydomain.com/FUZZ -rf request.txt  # reuse the request headers and body
```

</details>

***

### Method (`-X`)

HTTP method to use for request GET, POST, PUT, DELETE, etc...

<details>

<summary>Example: use <code>POST</code> method for fuzzing</summary>

```bash
secator x ffuf mydomain.com -X POST
```

</details>

***

### Data (`-data`)

Data to send in the request body.

<details>

<summary>Example: send JSON data in POST request</summary>

```bash
secator x ffuf mydomain.com/api -X POST -data '{"key":"value"}'
```

</details>

***

### User-agent (`-ua`)

Custom user-agent to use for request.

<details>

<summary>Example: use <code>secator</code> as a user agent value</summary>

```bash
secator x dalfox mydomain.com -ua secator
```

</details>

***

### **Match regex (`-mr)`**

Keep responses which body content match the input.

<details>

<summary>Example: keep responses which match the regex<code>MySQLError.*</code></summary>

```bash
secator x ffuf mydomain.com -mr MySQLError.*
```

</details>

***

### Match size (`-ms`)

Keep responses which body size (in bytes) match the input.

<details>

<summary>Example: keep responses with <code>1025</code> bytes</summary>

```bash
secator x katana mydomain.com -ms 1026  # bytes
```

</details>

***

### Match-words (`-mw)`

Keep responses which body word count match the input.

<details>

<summary>Example: keep responses with <code>10</code> words</summary>

```bash
secator x katana mydomain.com -mw 10
```

</details>

***

### Match code (`-mc`)

Keep responses which HTTP status codes match the input.

<details>

<summary>Example: keep responses matching HTTP statuses <code>200</code>,<code>400</code>,<code>501</code></summary>

```bash
secator x katana mydomain.com -mc 200,400,501
```

</details>

***

### Filter regex (`-fr`)

Filter out responses which body content match the input.

<details>

<summary>Example: filter out responses containing the string <code>LoginPage</code></summary>

```bash
secator x ffuf mydomain.com -fr LoginPage.*
```

</details>

***

### Filter codes (`-fc`)

Filter out responses which HTTP status codes match the input.

<details>

<summary>Example: filter out responses matching HTTP status <code>500</code></summary>

```bash
secator x ffuf mydomain.com -fc 500
```

</details>

***

### Filter size (`-fs)`

Filter out responses which body size (in bytes) match the input.

<details>

<summary>Example: filter out responses with <code>1025</code> bytes</summary>

```bash
secator x ffuf mydomain.com -fs 1025
```

</details>

***

### Filter words (`-fw`)

Filter out responses which body word count match the input.

<details>

<summary>Example: filter out responses with <code>10</code> words</summary>

```bash
secator x ffuf mydomain.com -fw 10
```

</details>

***

### Follow redirect (`-frd`)

Follow all http redirects.

<details>

<summary>Example: follow HTTP redirects</summary>

```bash
secator x katana mydomain.com -frd
```

</details>

***

### Depth (`-depth`)

Scan depth for crawling tasks.

<details>

<summary>Example: set crawl depth to <code>3</code></summary>

```bash
secator x gospider mydomain.com -depth 3
```

</details>

***

### Replay proxy (`-P`)

Proxy to use for replay requests (useful for fuzzing tasks).

<details>

<summary>Example: use a proxy for replay requests</summary>

```bash
secator x ffuf mydomain.com/FUZZ -P http://localhost:8080
```

</details>

***

### Wordlist (`-w`)

Custom wordlist to use.

<details>

<summary>Example: use fuzz-Bo0oM wordlist</summary>

```bash
secator x ffuf mydomain.com/FFUF/ -w /usr/share/seclists/Fuzzing/fuzz-Bo0oM.txt
```

</details>

***

## Port Scanning Options

The following options apply to port scanning tasks (e.g., `naabu`, `nmap`).

### Ports (`-p`)

Only scan specific ports. Accepts a comma-separated list of ports, or `-` for all ports.

<details>

<summary>Example: scan ports <code>80</code>, <code>443</code>, and <code>8080</code></summary>

```bash
secator x naabu mydomain.com -p 80,443,8080
```

</details>

***

### Top ports (`-tp`)

Scan the N most common ports.

<details>

<summary></summary>

```bash
secator x naabu mydomain.com -tp 100
```

</details>

***


# Input formats

... or how to pass targets to secator.

`secator` is built to be flexible in terms of input formats.

***

## **Direct input**

Inputs can be passed directly as an argument to the command / workflow / scan you wish to run:

```sh
secator x httpx example.com # single input
secator x httpx example.com,example2.com,example3.com # multiple comma-separated inputs
```

***

## **File input**

Input can also be passed from a file containing one item per line:

```sh
secator x httpx urls.txt
```

***

## **Stdin input**

Input can also be passed directly from `stdin`:

```sh
cat urls.txt | secator x httpx
```

You can build basic workflow using UNIX pipes:

```bash
secator x subfinder vulnweb.com | secator x nmap | secator x httpx
```

{% hint style="info" %}
For more complex workflows, we highly recommend using the YAML-based workflow definitions or the code-based workflow definitions (see [Writing workflows](/for-developers/writing-workflows)).
{% endhint %}

***


# Output options

... or how to change secator's console output.

`secator` is built to be flexible in terms of output options.

***

### Console

The default `secator` output is the [Output types](/in-depth/concepts/output-types) `repr` function. It is supposed to be pretty and readable to quickly understand `secator`'s findings:

<div align="left"><figure><img src="https://3064929067-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F787L9q3BUjAZ71WVY90V%2Fuploads%2F4oGXN98frKKxJSt8g8aM%2F2023-07-05%2015_47_29-%E2%97%8F%20%F0%9F%94%93%20mydomain.com_554%20%E2%80%A2%20Untitled-1%20-%20Untitled%20(Workspace)%20-%20Visual%20Studio%20Code.png?alt=media&amp;token=bca89978-6d09-4845-8b32-579a33af46eb" alt=""><figcaption><p>Console output</p></figcaption></figure></div>

Unicode icons are printed before each result to distinguish each output type:

* [Output types](/in-depth/concepts/output-types#exploit)
* [Output types](/in-depth/concepts/output-types#ip)
* [Output types](/in-depth/concepts/output-types#port)
* [Output types](/in-depth/concepts/output-types#record)
* [Output types](/in-depth/concepts/output-types#subdomain)
* [Output types](/in-depth/concepts/output-types#tag)
* [Output types](/in-depth/concepts/output-types#technology)
* [Output types](/in-depth/concepts/output-types#url)
* [Output types](/in-depth/concepts/output-types#useraccount)
* [Output types](/in-depth/concepts/output-types#vulnerability)

When an output type has a low `confidence`, the output will be dimmed:

<div align="left" data-full-width="false"><figure><img src="https://3064929067-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F787L9q3BUjAZ71WVY90V%2Fuploads%2FztkeQidpB8jS66NodqnS%2Fvuln_dimmed.png?alt=media&amp;token=f79373e7-622b-462f-98af-b91962921161" alt=""><figcaption></figcaption></figure></div>

***

### JSON lines (`-json`)

You can use `-json` to output live results as JSON lines:

```bash
secator x httpx example.com -json
```

{% hint style="info" %}
JSON lines output is pipeable / streameable to other tools like `jq`.
{% endhint %}

***

### Raw (`-raw`)

You can use `-raw`to output live results in plaintext format:

```
secator x httpx example.com -raw
```

{% hint style="info" %}
Raw output is saveable to txt files or can be used for chaining tasks using UNIX pipes.
{% endhint %}

***

### Custom format (`-fmt`)

You can use `-fmt`to output live results in a format of your choice:

```bash
secator x naabu example.com -fmt '{host}:{port} -> {service_name}'
```

{% hint style="info" %}
Custom formatting is based on the [Output types](/in-depth/concepts/output-types) fields.
{% endhint %}

***


# Philosophy & design

... or the core concepts behind the pillars of secator.

***

## Why do we need another tool ?

Traditional pentesting sessions can be a pain:

* Use of dozens of commands (each very good at what it does)
* Each command has different options
* Each command outputs results in a different format

Here is for instance a basic host scan that we run day-to-day on many targets:

{% tabs %}
{% tab title="Standard" %}

<pre class="language-bash"><code class="lang-bash"><strong>naabu -Pn -silent -host example.com -json -rate 10 -c 50 > open_ports.txt
</strong>nuclei -silent -sj -si 20 -hm -u example.com -jsonl -tags network,ssl -proxy socks5://tor-privoxy:9050 -rate-limit 10 -c 50
httpx -silent -td -asn -cdn -l open_ports.txt -json -proxy socks5://tor-privoxy:9050 -rate-limit 10 -threads 50 -match-code 200,204,301,302,307,401,403,405,500
katana -silent -jc -js-crawl -known-files all -u example.com -json -proxy socks5://tor-privoxy:9050 -rate-limit 10 -concurrency 50
echo https://example.com | cariddi -info -s -err -e -ext 1 -json -proxy socks5://tor-privoxy:9050 -c 50
</code></pre>

{% endtab %}

{% tab title="Secator" %}

```bash
secator s host example.com -json -proxy socks5://tor-privoxy:9050 -rl 10 -threads 50 -mc 200,204,301,302,307,401,403,405,500
```

{% endtab %}
{% endtabs %}

With `secator` the goal is to unify all these awesome tools by creating an abstract layer for input and output, such that all tools "speak" the same language and we can mutualize options that will apply to all tools, unlocking the ability to run complex workflows.

***

## Who is it made for ?

* [x] Bug-bounty hunters
* [x] Pentesters
* [x] Security researchers
* [x] Companies of any size

***

## Why is the license BSL ?

We believe in open-source 100%.

**Freelabz** founders have written code for open-source for as long as they started working in the tech industry. `secator` is and will remain free-to-use, forkable and open to community contributions forever, and we believe in the collective to make it one of the de-facto tools in the security world.

However, **Freelabz** is a young company that is seeking to make a living and pay its workers correctly. We are working on a paid product derived from `secator` and BSL allows us to restrict commercial uses of `secator` by big tech actors who could profit from its success. All standard OSS rules still apply, which means you can still read / fork / modify it, use it in other OSS projects, and even use it in production **as long as you don't sell a service based on it**.

Feel free to reach out to us if you want to use `secator` in a commercial tool, and we will review your request on a case-by-case basis.

***

## Design principles

### **Curated list of tools**

Tools integrated to `secator` <mark style="color:red;">**MUST**</mark> be <mark style="color:orange;">**fast**</mark>, <mark style="color:orange;">**efficient**</mark>, <mark style="color:orange;">**well-maintained**</mark>, and have <mark style="color:orange;">**structured output**</mark> (either `JSON`, `JSON lines`, `CSV`, or `XML`).

{% hint style="info" %}
We do make exceptions for really awesome tools and write custom parsers (e.g: `nmap`).
{% endhint %}

### **Unified input options**

`secator` tools belonging to the same category (eg: fuzzers) <mark style="color:red;">**MUST**</mark> end up with <mark style="color:orange;">**mutualized**</mark> [<mark style="color:orange;">**input options**</mark>](/runner-options/meta-options), while still retaining the capability to use unique options for each command.

### **Unified output schema**

Tools belonging to the same category <mark style="color:red;">**MUST**</mark> have <mark style="color:orange;">**unified**</mark> [<mark style="color:orange;">**output types**</mark>](/in-depth/concepts/output-types), allowing you to run multiple commands and aggregate results quickly.

### **CLI and library usage**

When `secator` is called as a library from other Python code, the output <mark style="color:red;">**MUST**</mark> be <mark style="color:orange;">**structured**</mark> (list of dicts). Results <mark style="color:red;">**MUST**</mark> also be yielded in <mark style="color:orange;">**realtime**</mark>.

When `secator` is called as a CLI, various [Exporters](/in-depth/concepts/exporters) <mark style="color:red;">**MUST**</mark> be available, such as <mark style="color:orange;">**csv**</mark>, <mark style="color:orange;">**json**</mark>, <mark style="color:orange;">**txt**</mark>, or <mark style="color:orange;">**table**</mark>.

### **Distributed options**

`secator` <mark style="color:red;">**MUST**</mark> work in both <mark style="color:orange;">**synchronous**</mark> mode (default) and <mark style="color:orange;">**distributed**</mark> mode.

Switch from synchronous to distributed when you want to increase the scanning speed <mark style="color:red;">**MUST**</mark> be easy, by simply configuring Celery worker with your broker and results backend of choice.

### **From simple tasks to complex workflows**

`secator` <mark style="color:red;">**MUST**</mark> be useful for <mark style="color:orange;">**running simple tasks**</mark> like in CTFs, bug-bounties or hackathon, or to <mark style="color:orange;">**automate entire workflows**</mark>.

### **Customizable**

`secator` <mark style="color:red;">**MUST**</mark> be <mark style="color:orange;">**customizable**</mark>, so that the community can contribute tasks, workflows, and scans to the repo if they can serve the greater good.

***


# Distributed runs with Celery

... or how you can 10x your scanning speed and massively parallelize your workflows.

By default, `secator` runs all tasks synchronously. This guide shows how to enable distributed runs using Celery workers, which unlocks [Writing workflows](/for-developers/writing-workflows#concurrent-tasks).

***

## Prerequisite

To use distributed runs, make sure the `worker` addon is installed:

```bash
secator install addons worker
```

## Step 1: Configure a broker and result backend \[optional]

{% hint style="info" %}
This step is **optional**. If you do not configure a broker, the **file system** will be used as a broker and result backend. Note that this works only if the client and worker run on the same VM.
{% endhint %}

You can set up a task queue using Celery with the broker and a results backend of your choice, and run Celery workers to execute tasks from the broker queue.

The following is an example using `redis`, but you can use any [supported Celery broker and backend](https://docs.celeryq.dev/en/stable/getting-started/backends-and-brokers/index.html).

**Install `redis` addon:**

```bash
secator install addons redis
```

**Install `redis`:**

```sh
sudo apt install redis
```

**Start `redis` and enable at boot:**

```sh
sudo systemctl enable redis
sudo systemctl start redis
```

**Configure `secator` to use Redis:**

<pre class="language-sh"><code class="lang-sh"><strong>secator config set celery.broker_url redis://&#x3C;REDIS_IP>:6379/0
</strong>secator config set celery.result_backend redis://&#x3C;REDIS_IP>:6379/0
</code></pre>

{% hint style="warning" %}
Make sure you replace `<REDIS_IP>` in the variables above with the IP of your Redis server.
{% endhint %}

***

## **Step 2: Start a Celery worker**

```sh
secator worker
```

Useful options (see `secator worker --help`):

```sh
secator worker -c 4                  # 4 child processes
secator worker -Q small,medium       # only consume some queues
secator worker -n worker1@%h         # set a unique worker hostname
secator worker --check               # check that a worker is alive
```

***

## **Step 3: Run a task, workflow or scan**

{% tabs %}
{% tab title="CLI" %}

```bash
secator x httpx wikipedia.org
secator w host_recon wikipedia.org
secator s host wikipedia.org
```

{% endtab %}

{% tab title="Python" %}

```python
from secator.tasks import httpx
from secator.workflows import host_recon
from secator.scans import host

target = 'testphp.vulnweb.com'

for result in httpx(target, sync=False):
  print(result)

for result in host_recon(target, sync=False):
  print(result)
  
for result in host(target, sync=False):
  print(result)
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
If you want to run synchronously (bypassing the broker), you can use the `--sync` flag (CLI) or the `sync` kwarg (Python).
{% endhint %}

***

## Worker queues

Tasks are routed to Celery queues named after their resource **profile**:

| Queue         | Used for                                                                                       |
| ------------- | ---------------------------------------------------------------------------------------------- |
| `small`       | Lightweight tasks (default for most tasks).                                                    |
| `medium`      | Heavier tasks (for instance `search_vulns`, `wpscan`, `trufflehog`).                           |
| `large`       | Resource-hungry runs (for instance `httpx` with screenshots, or fuzzers with large wordlists). |
| `extra_large` | Very heavy tasks (for instance `katana` in headless mode).                                     |

Workflow and scan orchestration runs on the `celery` queue, result aggregation on the `results` queue, polling on the `poll` queue and MongoDB driver jobs on the `mongodb` queue: make sure at least one worker consumes each of them. By default `secator worker` consumes all queues, so a single worker handles everything. To size workers differently, run one worker pool per queue with `-Q`, for instance:

```sh
secator worker -Q celery,results,poll,mongodb,small -c 8
secator worker -Q medium,large,extra_large -c 2
```

To send a task to another queue, override its profile (see [Configuration](/getting-started/configuration#task-overrides)):

```sh
secator config set tasks.overrides.nmap.profile large
```

{% hint style="info" %}
Task timeouts, memory limits and redelivery of tasks whose worker died are controlled by the `celery.task_*` and `celery.worker_*` options listed in the [Configuration reference](/getting-started/configuration/configuration-reference).
{% endhint %}

***


# AI task

... or how to run an autonomous, permission-gated pentest agent with secator.

The `ai` task is an autonomous security assistant that drives `secator` from natural language. You give it a target and a prompt; it reasons in a loop, runs `secator` tasks and workflows, executes shell commands, queries your workspace findings, records results, and explains what it found - all under a permission engine you control.

It is an ordinary `secator` task, so everything you know about targets, drivers, workspaces, and output already applies:

```bash
secator x ai testphp.vulnweb.com -p "Scan this host for vulnerabilities"
```

{% hint style="warning" %}
**Only run the `ai` task against targets you are authorized to test.** The agent can actively scan, fuzz, and attempt exploitation. Keep it inside a scope you own or have explicit permission to assess (see [Scope](#scope)).
{% endhint %}

***

## Installation

The AI capabilities ship as an addon. Install it, then run the setup wizard to pick a model and store your API key:

```bash
secator install addons ai
secator x ai setup
```

If the addon is missing, the task exits with a reminder to run `secator install addons ai`.

***

## Execution paths: TUI vs headless

How a run behaves depends on whether you launch it interactively (with a TTY) or headless. Interactively, the task opens a full-screen **TUI** where you watch the agent reason, answer its permission prompts, and toggle its mode/guardrails live. Headless (no TTY, or driven from a calling application / another task), it runs the same agent loop but resolves questions through the configured [`--interactive`](#interactive-modes) backend instead of the terminal. Pass `--no-tui` to force the headless loop even on a TTY.

```mermaid
flowchart TD
    A["secator x ai TARGET -p '...'"] --> B{"interactive TTY?"}
    B -- yes --> C["TUI"]
    B -- no --> D["headless loop"]
    C --> E["live view: reasoning, actions, results"]
    C --> F["shift+tab: toggle bypass (dangerous)"]
    C --> G["ctrl+t: cycle mode (auto/scan/chat/exploit)"]
    C --> H["permission prompts answered in-terminal"]
    D --> I["--interactive local|remote|auto resolves questions"]
    E --> J["agent loop: plan -> act -> observe"]
    I --> J
    J --> K{"action allowed by permission engine?"}
    K -- allow --> L["run action"]
    K -- deny --> M["blocked, agent informed"]
    K -- ask --> N["prompt operator"]
    L --> J
    N --> J
```

The TUI exposes two live toggles documented below: **shift+tab** toggles [Dangerous mode (`--dangerous`)](#dangerous-mode) and **ctrl+t** cycles the [mode (`--mode`)](#modes).

***

## Configuration

Configure the addon under the `addons.ai.*` keys. Every key is also settable via an environment variable of the form `SECATOR_ADDONS_AI_<KEY>`.

```bash
secator config set addons.ai.default_model claude-sonnet-4-6
secator config set addons.ai.api_key sk-...
```

| Key                            | Default                                             | What it does                                                                                                                                                                   |
| ------------------------------ | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `api_key`                      | `''`                                                | API key for the LLM provider. Env: `SECATOR_ADDONS_AI_API_KEY`.                                                                                                                |
| `api_base`                     | `''`                                                | Base URL of the LLM endpoint. Leave empty for the provider default. Env: `SECATOR_ADDONS_AI_API_BASE`.                                                                         |
| `default_model`                | `claude-sonnet-4-6`                                 | Model used for the main agent loop.                                                                                                                                            |
| `intent_model`                 | `claude-haiku-4-5`                                  | Smaller/cheaper model used only to classify your prompt into a mode.                                                                                                           |
| `temperature`                  | `0.7`                                               | Sampling temperature for the agent.                                                                                                                                            |
| `max_tokens`                   | `30000`                                             | Per-response token cap.                                                                                                                                                        |
| `max_tokens_total`             | `100000`                                            | Hard conversation cap; the oldest messages are truncated beyond this.                                                                                                          |
| `max_results`                  | `500`                                               | Max workspace results returned to the agent per query.                                                                                                                         |
| `max_iterations`               | `25`                                                | Max agent-loop iterations. Set to `-1` (or any value `<= 0`) to **disable the cap** - the run continues until the model stops on its own (bounded by a high internal ceiling). |
| `context_window`               | `128000`                                            | Fallback context-window size for a model litellm doesn't know about.                                                                                                           |
| `user_response_timeout`        | `600`                                               | Seconds the agent waits for an interactive answer before giving up.                                                                                                            |
| `encrypt_pii`                  | `true`                                              | Redact detected PII (emails, IPs, hostnames) before sending text to the LLM.                                                                                                   |
| `custom_disallow_config_token` | `false`                                             | When on, a caller-overridden `api_base` must come with its own `api_key` - the configured key is never reused on a different endpoint.                                         |
| `permissions`                  | *(see* [*Permission engine*](#permission-engine)*)* | The allow/deny/ask rules the agent runs under.                                                                                                                                 |

Most of these have a matching run option so you can override them per run - e.g. `--model`, `--api-base`, `--api-key`, `--temperature`, `--max-iterations`, `--max-tokens-total`, `--intent-model`.

***

## Models and providers

The `ai` task uses [litellm](https://github.com/BerriAI/litellm) under the hood, so any model litellm supports works - just set `default_model` (and the matching provider key) accordingly.

To point the agent at a local or OpenAI-compatible endpoint (e.g. a self-hosted model server), pass `--api-base` and `--model`:

```bash
secator x ai testphp.vulnweb.com \
  -p "Summarize the open ports" \
  --model openai/my-local-model \
  --api-base http://localhost:8000/v1 \
  --api-key sk-local
```

{% hint style="info" %}
For a model litellm does not have registry info for (e.g. a custom/self-hosted one), the task warns and falls back to `addons.ai.context_window` for the context-window size. Set that config key to your model's real window.
{% endhint %}

***

## Modes

The agent operates in one of three modes. Each mode controls which actions the agent is even allowed to attempt:

| Mode      | What it can do                                                                                                                                                                        |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `chat`    | **Read-only.** Read and explain workspace data (`query_workspace`), ask follow-ups, delegate a read-only subagent, and stop. It cannot run shell, tasks/workflows, or write findings. |
| `scan`    | Actively scan: run tasks/workflows and shell commands, exploit, and write or curate findings.                                                                                         |
| `exploit` | Verify a single vulnerability by actually exploiting it and record a proof-of-concept.                                                                                                |

Select a mode explicitly with `--mode`:

```bash
secator x ai testphp.vulnweb.com -p "Enumerate the web app" --mode scan
```

In the TUI you can change mode live with **ctrl+t**.

{% hint style="info" %}
The `scan` mode was previously called `attack`. The old name is still accepted as an alias and maps to `scan`.
{% endhint %}

### Auto mode and self-escalation

If you don't pass `--mode` (or pass `auto`), the mode is **auto-detected** from your prompt and **re-classified every turn**, so the agent follows the conversation (chat ↔ scan ↔ exploit). A fast deterministic pre-classifier handles unambiguous prompts; anything ambiguous is sent to the small `intent_model`.

In an auto session the agent may **self-escalate** to a more capable mode when the task needs it, via the `change_mode` action - for example, moving from read-only chat into scan when you ask it to actually run a tool. Two guardrails apply:

* An auto session **never silently de-escalates to `chat`** once it has entered an action mode (a mid-engagement aside won't strip its ability to record a finding).
* A **user-pinned `chat`** (you passed `--mode chat`) stays read-only - the agent cannot self-escalate out of it; it can only suggest that *you* switch.

A concrete `--mode` you pass is **pinned**: it sticks for the whole session and the agent will not silently escalate out of it.

***

## Prompts and context

Provide the task with a prompt via `-p/--prompt`. The prompt may be a literal string or a path to a file (local/CLI runs only):

```bash
secator x ai testphp.vulnweb.com -p "Find and rank the exploitable vulnerabilities"
secator x ai testphp.vulnweb.com -p ./prompts/engagement.txt
```

If you run interactively with no prompt, the task opens a full-screen prompt so you can type one.

Pass structured context (findings, objective, scope hints) with `--context` as a dict.

***

## Interactive modes

`--interactive` selects how the agent asks you questions mid-run:

* `local` (default for synchronous CLI runs) - prompts you in the terminal.
* `remote` - reads/writes answers through the workspace store, for headless runs driven by a calling application.
* `auto` - picks a backend automatically.

***

## Sessions and resume

Each run is a session. Name one with `-n/--name`, and resume a previous session with `--resume`:

```bash
secator x ai testphp.vulnweb.com -p "Scan this host" --name webapp-assessment
secator x ai --resume
```

`--resume` shows a picker of prior sessions, restores the conversation and its findings, and continues where you left off (re-detecting the mode from your next message when the session was in auto mode).

***

## Subagents

In `scan`/`exploit` modes the agent can spawn an **autonomous subagent** (the `run_subagent` action) to work a focused objective in a fresh context window and hand back a summary. Each subagent runs with its **own conversation id** (its transcript is separate from the parent's) and inherits the parent's remaining iteration budget. Subagents run non-interactively and do not self-escalate.

***

## Tools and actions

Depending on the mode, the agent can call the following actions:

| Action                     | Purpose                                                                               |
| -------------------------- | ------------------------------------------------------------------------------------- |
| `run_task`                 | Run a single `secator` task (e.g. `nmap`, `httpx`, `nuclei`, `ffuf`) against targets. |
| `run_workflow`             | Run a `secator` workflow (a composed sequence of tasks).                              |
| `run_shell`                | Run an arbitrary shell command for exploration, exploitation, or data analysis.       |
| `query_workspace`          | Query stored findings with MongoDB-style queries (every query must be targeted).      |
| `run_subagent`             | Spawn an autonomous subagent for a sub-objective.                                     |
| `follow_up`                | Ask you a clarifying question or present options.                                     |
| `add_finding`              | Record a new finding (vulnerability, exploit, URL, ...) in the workspace.             |
| `mark_vuln_exploited`      | Mark an existing vulnerability as exploited and attach a proof-of-concept.            |
| `mark_vuln_exploit_failed` | Mark a real vulnerability it could not exploit this attempt.                          |
| `mark_vuln_false_positive` | Mark an existing vulnerability as a false positive.                                   |
| `update_finding`           | Update fields (severity, tags, CVEs, extra data) on an existing finding.              |
| `change_mode`              | Switch its own operating mode (auto / pinned-action sessions only).                   |
| `stop`                     | End the turn when the request is fulfilled or blocked.                                |

`chat` mode is restricted to `query_workspace`, `follow_up`, `run_subagent`, `change_mode`, and `stop`.

Run tasks/workflows through the worker instead of inline with `--async-tasks`, and tune batch concurrency with `--max-workers`.

***

## Output

The agent emits `Ai` output items for its prompts, reasoning/responses, each action, tool results, follow-up questions, and stops, alongside the normal `secator` finding output types (vulnerabilities, exploits, URLs, ports, ...) that its tasks produce. Findings are persisted to the workspace like any other `secator` result, so you can query them afterwards with `secator q`.

***

## Permission engine

Every action the agent attempts is checked against an allow / deny / ask rule set before it runs. This is the core safety boundary, and it combines with the optional [scope](#scope) boundary and [shell isolation](#shell-isolation).

```mermaid
flowchart TD
    A["agent wants to run_shell / reach target / read / write"] --> B{"--dangerous?"}
    B -- "yes (bypass)" --> Z["ALLOW everything, no prompt (scope also dropped)"]
    B -- no --> C{"target in scope?<br/>(deny wins over allow)"}
    C -- out_of_scope --> D["deny (or ask, unless scope_hard_deny)"]
    C -- in_scope / n/a --> E{"permission rule?"}
    E -- deny --> F["blocked"]
    E -- allow --> G["run"]
    E -- ask --> H["prompt operator"]
    H -- approved --> G
    H -- refused --> F
    G --> I{"--isolated?"}
    I -- yes --> J["run_shell inside per-runner Docker sandbox"]
    I -- no --> K["run on host"]
```

Rules are grouped under `addons.ai.permissions` into `allow`, `deny`, and `ask` lists. Each rule names an action class and a set of patterns:

* `target(<hosts/urls>)` - network targets the agent may reach.
* `read(<paths>)` - filesystem paths it may read.
* `write(<paths>)` - filesystem paths it may write.
* `shell(<commands>)` - shell command names it may run.
* `task(<names>)` / `workflow(<names>)` - runners it may invoke.

Two placeholders expand at runtime: `{targets}` (the run's targets) and `{workspace}` (the run's workspace path). A leading `~/` expands to your home directory.

**Evaluation order is `deny > allow > ask > default-deny`.** A deny always wins; an unmatched action falls through to `ask` (you're prompted), and if nothing matches at all it is denied.

The built-in defaults, roughly:

* **allow** the run's own targets and GitHub (for public PoC/exploit clones), reads/writes inside the workspace and `/tmp`, a curated list of common read-only and recon shell commands, and all tasks/workflows.
* **deny** cloud metadata/loopback targets, reads of host secrets (`/etc/shadow`, `~/.ssh/*`, `~/.aws/*`), writes to system paths, and destructive commands.
* **ask** for anything else - arbitrary targets, interpreter/compiler shell commands (`python`, `bash`, `node`, `gcc`, ...), and arbitrary reads/writes.

The agent is also told in its system prompt never to touch host secrets, environment variables, the `secator` config, or the workspace database - exploiting the *target* is in scope; exploiting the host running `secator` is not, and such actions are blocked regardless of what the prompt says.

### Guardrail levels at a glance

The flags combine into escalating trust levels - from "gate everything" to "trust the LLM". Each row shows which guardrail layers still apply:

| Mode                                |   Command  |    Path    |      Target / scope     | Approval prompts |  Shell isolation |
| ----------------------------------- | :--------: | :--------: | :---------------------: | :--------------: | :--------------: |
| **default**                         |   ✅ gated  |   ✅ gated  |         ✅ gated         |        yes       |        no        |
| **`--in-scope` / `--out-of-scope`** |      ✅     |      ✅     | ✅ in-scope pre-approved |      fewer¹      |        no        |
| **`--isolated`**                    | ❌ dropped² | ❌ dropped² |     ✅ still enforced    |   targets only   | ✅ Docker sandbox |
| **`--dangerous`**                   |      ❌     |      ❌     |   ❌ scope dropped too   |       none       |        no        |

¹ In-scope targets are pre-approved, so they don't prompt; out-of-scope targets are denied (or prompt, unless `scope_hard_deny` is set). ² The sandbox container *is* the command/filesystem boundary, so those layers drop to *allow* - but network egress (targets) and sensitive-env access still gate.

### Dangerous mode

`--dangerous` skips **all** permission checks. Use it only in a throwaway, fully isolated environment you control:

```bash
secator x ai testphp.vulnweb.com -p "..." --dangerous
```

In the TUI you can toggle it live with **shift+tab** (it flips the "bypass guardrails" / "guardrails enforced" banner).

{% hint style="danger" %}
`--dangerous` removes the entire safety boundary. It **also disables scope filtering** - `--in-scope` / `--out-of-scope` are intentionally dropped when `--dangerous` is on, so nothing is filtered as out-of-scope and child runners inherit no scope boundary. The agent can then run any shell command, reach any target, and read/write any path the process can. Never use it against a shared host or a target you don't fully own.
{% endhint %}

***

## Scope

On top of the permission rules you can give the run a hard target boundary with `--in-scope` (an allow-list) and `--out-of-scope` (a deny-list). Deny wins over allow, and out-of-scope targets are dropped from the run inputs entirely:

```bash
secator x ai app.example.com \
  -p "Assess this app" \
  --in-scope app.example.com,api.example.com \
  --out-of-scope admin.example.com
```

In-scope hostnames are resolved to their current IPs so the agent can reach an in-scope host by either name or IP. The scope applies both to the AI run itself and to **every child task/workflow the agent spawns** - each child runner force-inherits the parent's `in_scope` / `out_of_scope`, so the agent cannot escape the boundary by delegating. The active scope is injected into the system prompt so the agent knows its boundaries and won't retry a denied target.

By default an out-of-scope network target that the agent tries falls through to an interactive `ask`. Setting `security.scope_hard_deny` (`SECATOR_SECURITY_SCOPE_HARD_DENY=1`) makes any out-of-scope network target a hard deny instead of a prompt.

{% hint style="warning" %}
Scope only applies while the permission engine is active. As noted above, [`--dangerous`](#dangerous-mode) currently disables scope filtering entirely.
{% endhint %}

***

## Shell isolation

`--isolated` runs every `run_shell` command inside a **per-run Docker container** (Docker-in-Docker) instead of on the host. The container becomes the command/filesystem boundary, so shell-command and path permission prompts are dropped - but **target (network egress) and sensitive-env checks still apply**. The sandbox container is torn down when the turn ends.

```bash
secator x ai testphp.vulnweb.com -p "..." --mode exploit --isolated
```

This requires Docker to be available. The default comes from `security.shell_isolated` (`SECATOR_SECURITY_SHELL_ISOLATED=1`), so a sandboxed worker can force isolation on for every AI run.

***

## PII encryption

By default (`--sensitive`, config `addons.ai.encrypt_pii`) the task redacts detected PII - emails, IPv4 addresses, and hostnames - replacing them with reversible placeholders before any text is sent to the LLM, and restoring the originals in output. Disable with `--no-sensitive` if you need the raw values in the transcript (for example when the model must reason about exact hostnames).

***

## Guardrails and limits

Several limits keep a run bounded and observable:

* **Iteration cap** - `--max-iterations` (default `25`). `-1` disables the user cap but a high internal ceiling still bounds a runaway loop. Each mode also imposes a floor.
* **Token caps** - `max_tokens` per response and `max_tokens_total` for the whole conversation (oldest messages truncated beyond it).
* **Context warnings** - on by default (`--context-warnings` / `--no-context-warnings`); the task warns as the context window fills (around 50% / 75%) and auto-summarizes older history past a high threshold.

***

## Natural-language queries with `secator q`

`secator q <arg>` resolves its argument in three steps - a saved query name, a filter expression, and otherwise **natural language**, which is routed to the `ai` task in `chat` mode:

```bash
secator q "Show me the top 5 vulnerabilities in my workspace"
secator q "Analyze my workspace data"
```

This is the quickest way to ask read-only questions about your findings without typing a full `secator x ai ... --mode chat` command (run `secator x ai setup` first to configure a model).

***

## Sample use case: scope, review, exploit

A realistic end-to-end session: hunt vulns on a scoped target, review findings in read-only chat, then exploit one in an isolated sandbox.

**1. Hunt, inside a hard scope.** Scan `app.example.com` but never let the agent (or its child runners) wander to the admin host:

```bash
secator x ai app.example.com \
  -p "Find and rank the exploitable vulnerabilities" \
  --mode scan \
  --in-scope app.example.com,api.example.com \
  --out-of-scope admin.example.com \
  --name example-engagement
```

The agent runs recon/scan tasks (nmap, httpx, nuclei, ...), records findings into the workspace, and stays within scope. Anything outside scope is blocked; anything the permission engine is unsure about prompts you.

**2. Review, read-only.** Ask questions about what it found without any risk of a write or scan - `chat` is read-only:

```bash
secator x ai app.example.com -p "What are the top 3 findings and why?" --mode chat
```

(You can also use `secator q "Show me the top 5 vulnerabilities in my workspace"`, which routes natural language to the `ai` task in `chat` mode.)

**3. Exploit one, in isolation.** Verify a single vulnerability by actually exploiting it, with every shell command sandboxed in a disposable Docker container:

```bash
secator x ai app.example.com \
  -p "Prove exploitability of the SQLi on /search and record a PoC" \
  --mode exploit \
  --isolated
```

The agent attempts the exploit inside the sandbox, and on success calls `mark_vuln_exploited` to attach a proof-of-concept to the finding.

***

## Diagnostics

* `--show-prompt` renders the system prompt for the selected mode and exits - useful for inspecting exactly what the agent is told (including the active scope).
* `--dry-run` shows what the agent would do without executing actions.

```bash
secator x ai testphp.vulnweb.com --mode scan --show-prompt
```

***

## Library usage

Like any `secator` task, `ai` can be driven from Python:

```python
from secator.tasks import ai

task = ai('testphp.vulnweb.com', prompt='Scan this host for vulnerabilities', mode='scan')
for item in task:
    print(item)
```


# Concepts

... or foundational blocks that you must understand in order to go deeper.

This page presents various `secator` concepts:

* [Output types](/in-depth/concepts/output-types)
* [Proxies](/in-depth/concepts/proxies)
* [Exporters](/in-depth/concepts/exporters)
* [Runners](/in-depth/concepts/runners)
* [Drivers](/in-depth/concepts/drivers)
* [Profiles](/in-depth/concepts/profiles)
* [Workspaces](/in-depth/concepts/workspaces)

***


# Output types

... or how secator unifies all output into common schemas.

`secator` uses the notion of output types to uniformize tasks output. A task can output one or multiple output types.

The currently available output types are [#certificate](#certificate "mention"), [#domain](#domain "mention"), [#exploit](#exploit "mention"), [#ip](#ip "mention"), [#port](#port "mention"), [#record](#record "mention"), [#subdomain](#subdomain "mention"), [#tag](#tag "mention"), [#technology](#technology "mention"), [#url](#url "mention"), [#useraccount](#useraccount "mention"), [#vulnerability](#vulnerability "mention").

{% hint style="info" %}
Feel free to request more output types by [opening an issue](https://github.com/freelabz/secator/issues/new/choose) on the GitHub repo.
{% endhint %}

***

## 📜 Certificate

{% code title="secator/output\_types/certificate.py" %}

```python
@dataclass
class Certificate(OutputType):
    host: str
    fingerprint_sha256: str = field(default='')
    ip: str = field(default='', compare=False)
    raw_value: str = field(default='', compare=False)
    subject_cn: str = field(default='', compare=False)
    subject_an: list[str] = field(default_factory=list, compare=False)
    not_before: datetime = field(default=None, compare=False)
    not_after: datetime = field(default=None, compare=False)
    issuer_dn: str = field(default='', compare=False)
    issuer_cn: str = field(default='', compare=False)
    issuer: str = field(default='', compare=False)
    self_signed: bool = field(default=True, compare=False)
    trusted: bool = field(default=False, compare=False)
    status: str = field(default=CERTIFICATE_STATUS_UNKNOWN, compare=False)
    keysize: int = field(default=None, compare=False)
    serial_number: str = field(default='', compare=False)
    ciphers: list[str] = field(default_factory=list, compare=False)
```

{% endcode %}

***

## 🪪 Domain

{% code title="secator/output\_types/domain.py" %}

```python
@dataclass
class Domain(OutputType):
    domain: str
    alive: bool = False
    creation_date: datetime = field(default=None, compare=False)
    expiration_date: datetime = field(default=None, compare=False)
    updated_date: datetime = field(default=None, compare=False)
    status: list = field(default_factory=list, compare=False)
    registrar: str = field(default='', compare=False)
    registrar_info: dict = field(default_factory=dict, compare=False)
    registrant: str = field(default='', compare=False)
    registrant_info: dict = field(default_factory=dict, compare=False)
    administrative_info: dict = field(default_factory=dict, compare=False)
    technical_info: dict = field(default_factory=dict, compare=False)
    extra_data: dict = field(default_factory=dict, compare=False)
    is_false_positive: bool = field(default=False, compare=False)
    is_acknowledged: bool = field(default=False, compare=False)
    tags: list = field(default_factory=list, compare=False)
```

{% endcode %}

***

## ⍼ Exploit

{% code title="secator/output\_types/exploit.py" %}

```python
@dataclass
class Exploit(OutputType):
    name: str
    provider: str
    id: str
    matched_at: str = ''
    ip: str = ''
    confidence: str = 'low'
    reference: str = field(default='', repr=True, compare=False)
    cves: list = field(default_factory=list, compare=False)
    tags: list = field(default_factory=list, compare=False)
    extra_data: dict = field(default_factory=dict, compare=False)
    is_false_positive: bool = field(default=False, compare=False)
    is_acknowledged: bool = field(default=False, compare=False)
```

{% endcode %}

***

## 💻 Ip

{% code title="secator/output\_types/ip.py" %}

```python
@dataclass
class Ip(OutputType):
    ip: str
    host: str = field(default='', repr=True, compare=False)
    alive: bool = False
    protocol: str = field(default=IpProtocol.IPv4)
    extra_data: dict = field(default_factory=dict, compare=False)
    is_false_positive: bool = field(default=False, compare=False)
    is_acknowledged: bool = field(default=False, compare=False)
    tags: list = field(default_factory=list, compare=False)
```

{% endcode %}

***

## 🔓 Port

{% code title="secator/output\_types/port.py" %}

```python
@dataclass
class Port(OutputType):
    port: int
    ip: str
    state: str = 'UNKNOWN'
    service_name: str = field(default='', compare=False)
    cpes: list = field(default_factory=list, compare=False)
    host: str = field(default='', repr=True, compare=False)
    protocol: str = field(default='tcp', repr=True, compare=False)
    extra_data: dict = field(default_factory=dict, compare=False)
    confidence: str = field(default='low', repr=False, compare=False)
    service_confidence: str = field(default='low', repr=False, compare=False)
    is_false_positive: bool = field(default=False, compare=False)
    is_acknowledged: bool = field(default=False, compare=False)
    tags: list = field(default_factory=list, compare=False)
```

{% endcode %}

***

## 🎤 Record

{% code title="secator/output\_types/record.py" %}

```python
@dataclass
class Record(OutputType):
    name: str
    type: str
    host: str = ''
    extra_data: dict = field(default_factory=dict, compare=False)
    is_false_positive: bool = field(default=False, compare=False)
    is_acknowledged: bool = field(default=False, compare=False)
    tags: list = field(default_factory=list, compare=False)
```

{% endcode %}

***

## 🏰 Subdomain

{% code title="secator/output\_types/subdomain.py" %}

```python
@dataclass
class Subdomain(OutputType):
    host: str
    domain: str
    verified: bool = field(default=False, compare=False)
    sources: List[str] = field(default_factory=list, compare=False)
    extra_data: dict = field(default_factory=dict, compare=False)
    is_false_positive: bool = field(default=False, compare=False)
    is_acknowledged: bool = field(default=False, compare=False)
    tags: list = field(default_factory=list, compare=False)
```

{% endcode %}

***

## 🏷️ Tag

{% code title="secator/output\_types/tag.py" %}

```python
@dataclass
class Tag(OutputType):
    name: str
    value: str
    match: str
    category: str = field(default='general')
    extra_data: dict = field(default_factory=dict, repr=True, compare=False)
    stored_response_path: str = field(default='', compare=False)
    is_false_positive: bool = field(default=False, compare=False)
    is_acknowledged: bool = field(default=False, compare=False)
    tags: list = field(default_factory=list, compare=False)
```

{% endcode %}

***

## 📦 Technology

{% code title="secator/output\_types/technology.py" %}

```python
@dataclass
class Technology(OutputType):
    product: str
    match: str
    version: str = None
    extra_data: dict = field(default_factory=dict, repr=True, compare=False)
    tags: list = field(default_factory=list, compare=False)
```

{% endcode %}

***

## 🔗 Url

{% code title="secator/output\_types/url.py" %}

```python
@dataclass
class Url(OutputType):
    url: str
    host: str = field(default='', compare=False)
    verified: bool = field(default=False, compare=False)
    status_code: int = field(default=0, compare=False)
    title: str = field(default='', compare=False)
    protocol: str = field(default='', compare=False)
    webserver: str = field(default='', compare=False)
    tech: list = field(default_factory=list, compare=False)
    content_type: str = field(default='', compare=False)
    content_length: int = field(default=0, compare=False)
    time: str = field(default='', compare=False)
    method: str = field(default='', compare=False)
    words: int = field(default=0, compare=False)
    lines: int = field(default=0, compare=False)
    screenshot_path: str = field(default='', compare=False)
    stored_response_path: str = field(default='', compare=False)
    confidence: str = field(default='high', compare=False)
    response_headers: dict = field(default_factory=dict, repr=True, compare=False)
    request_headers: dict = field(default_factory=dict, repr=True, compare=False)
    extra_data: dict = field(default_factory=dict, compare=False)
    is_directory: bool = field(default=False, compare=False)
    is_root: bool = field(default=False, compare=False)
    is_redirect: bool = field(default=False, compare=False)
    is_false_positive: bool = field(default=False, compare=False)
    is_acknowledged: bool = field(default=False, compare=False)
    tags: list = field(default_factory=list, compare=False)
```

{% endcode %}

***

## 👤 UserAccount

{% code title="secator/output\_types/user\_account.py" %}

```python
@dataclass
class UserAccount(OutputType):
    username: str
    url: str = ''
    email: str = ''
    site_name: str = ''
    extra_data: dict = field(default_factory=dict, compare=False)
    is_false_positive: bool = field(default=False, compare=False)
    is_acknowledged: bool = field(default=False, compare=False)
    tags: list = field(default_factory=list, compare=False)
```

{% endcode %}

***

## 🚨 Vulnerability

{% code title="secator/output\_types/vulnerability.py" %}

```python
@dataclass
class Vulnerability(OutputType):
    name: str
    id: str = ''
    matched_at: str = ''
    provider: str = field(default='', compare=False)
    ip: str = field(default='', compare=False)
    confidence: str = field(default='low', compare=False)
    severity: str = field(default='unknown', compare=False)
    cvss_score: float = field(default=0.0, compare=False)
    cvss_vec: str = field(default='', compare=False)
    epss_score: float = field(default=0.0, compare=False)
    description: str = field(default='', compare=False)
    remediation: str = field(default='', compare=False)
    impact: str = field(default='', compare=False)
    poc: str = field(default='', compare=False)
    references: List[str] = field(default_factory=list, compare=False)
    reference: str = field(default='', compare=False)
    confidence_nb: int = field(default=0, compare=False)
    severity_nb: int = field(default=0, compare=False)
    extra_data: dict = field(default_factory=dict, compare=False)
    verified: bool = field(default=False, compare=False)
    is_false_positive: bool = field(default=False, compare=False)
    is_acknowledged: bool = field(default=False, compare=False)
    status: str = field(default='', compare=False)
    tags: list = field(default_factory=list, compare=False)
```

{% endcode %}

Notable fields:

* `severity_nb` / `confidence_nb`: numeric severity and confidence, handy for queries (`0` = critical, `1` = high, ...), e.g. `secator q "vulnerability.severity_nb < 2"`.
* `cvss_vec`, `epss_score`: CVSS vector and EPSS score, when the provider returns them.
* `description`, `impact`, `remediation`, `poc`: free-text details and proof-of-concept.
* `status`: triage status of the vulnerability. With the `mongodb` and `sqlite` drivers, it is carried over to the same vulnerability found again in later runs of the workspace, together with `is_false_positive`, `is_acknowledged`, `verified`, `tags` and `poc`.
* `tags`: vulnerabilities whose CVE is in the [CISA Known Exploited Vulnerabilities](https://www.cisa.gov/known-exploited-vulnerabilities-catalog) catalog are tagged `kev` automatically (a bundled copy of the catalog is used when offline), e.g. `secator q "vulnerability.tags ~= 'kev'"`.

***


# Proxies

... or how to control how secator connects to targets.

`secator` provides a range of proxy options to choose from, by passing the `-proxy` option to any runner (task, workflow, scan).

***

## Auto

Using `-proxy auto` for a `secator` run will result in auto-detecting the right proxy to pass to each task. This is the recommended option for most runs.

The defaults to use for proxies in `auto` mode are set through config variables:

* <pre class="language-bash"><code class="lang-bash"><strong>secator config set http.socks5_proxy socks5://tor-privoxy:9050
  </strong></code></pre>
* ```bash
  secator config set http.http_proxy http://tor-privoxy:8118
  ```

#### **Example**

Running `secator w host_recon <TARGET> -proxy auto` will result in the following behavior:

* `naabu` supports Socks5 / HTTP proxy, but not proxychains, so it will use the first available SOCKS5 / HTTP proxy.
* `nmap` supports proxy through `proxychains4` but has no good support for proxychains, HTTP or Socks5 proxy, so it will use `proxychains4` for the execution.
* ...

***

## HTTP

You can pass an HTTP proxy to a task / workflow / scan by using `-proxy http://<PROXY_IP>:<PROXY_PORT>`.

***

## Socks5

You can pass a SOCKS5 proxy to a task / workflow / scan by using `-proxy socks5://<PROXY_IP>:<PROXY_PORT>`.

***

## Proxychains

You can pass `-proxy proxychains` to a task / workflow / scan by using `-proxy proxychains`. Remember to configure your `/etc/proxychains.conf` in that configuration, and test it prior to running `secator` tasks.

***


# Exporters

... or how to export reports to different destinations.

`secator` exporters allow exporting **reports** at the end of the run.

{% hint style="info" %}
To export **results** in **real-time**, see [Drivers](/in-depth/concepts/drivers) instead.
{% endhint %}

Available exporters out-of-the-box are:

* **`txt`**: exports results as TXT files, one file per output type (`report_<type>.txt`).
* **`csv`**: exports results as CSV files, one file per output type (`report_<type>.csv`).
* **`json`**: exports results as a JSON file (`report.json`).
* **`jsonl`**: prints results as JSON lines to stdout, for piping to tools like `jq`.
* **`markdown`**: saves the reports written by the [AI task](/in-depth/ai-task) as `report_ai.md` (does nothing for runs without AI output).
* **`table`**: prints results as a table in the terminal.
* **`gdrive`**: exports results to Google Drive. Install the `gdrive` addon and set `addons.gdrive.credentials_path` and `addons.gdrive.drive_parent_folder_id` in your config for this exporter to work.

Reports are saved to the runner's report folder (`~/.secator/reports/<workspace>/<tasks|workflows|scans>/<id>/`).

The default exporters are `csv`, `txt` and `markdown` (see `tasks.exporters`, `workflows.exporters` and `scans.exporters` in the [Configuration reference](/getting-started/configuration/configuration-reference)).

{% hint style="info" %}
`json` is no longer a default exporter: the `report.json` file of each run is written live by the [Drivers](/in-depth/concepts/drivers#json-driver). Use `-o json` to also write the end-of-run JSON export.
{% endhint %}

***

## Using exporters

{% tabs %}
{% tab title="CLI" %}
To use exporters from the CLI, use the `--output` or `-o` flag:

```bash
secator x httpx mydomain.com -o txt
secator w host_recon mydomain.com -o gdrive,table
secator s url http://testphp.vulnweb.com -o csv,json
```

{% endtab %}

{% tab title="Python" %}
To use exporters from the library, you can pass the `exporters` kwarg to any runner:

```python
from secator.workflows import host_recon

host = 'mydomain.com'
workflow = host_recon(host, exporters=['csv', 'json'])
workflow.run()
```

{% endtab %}
{% endtabs %}

***

## Exporting existing reports

Exporters can also be used on past reports with `secator report show` and `secator query`:

```bash
secator r show tasks/3 -o csv -of /tmp/exports
secator q "vulnerability" -o jsonl | jq -r '.matched_at'
```

***

## Custom exporters

Drop a Python file defining an `Exporter` subclass in your templates folder (`~/.secator/templates/`). The exporter name is the class name without the `Exporter` suffix, lower-cased:

```python
# ~/.secator/templates/count_exporter.py
from secator.exporters._base import Exporter


class CountExporter(Exporter):
    def send(self):
        for output_type, items in self.report.data['results'].items():
            print(f'{output_type}: {len(items)}')
```

```bash
secator x httpx example.com -o count
```

***


# Runners

... or how secator's internals work.

A runner is at the core of `secator` live processing capabilities. It handles the parsing, converting and processing of input options (CLI and library) and output items.

All runners inherit from `secator.runners._base.Runner`.

***

## Supported runners

Some built-in runners are available out-of-the-box:

<table><thead><tr><th width="134">Runner</th><th>Description</th><th>Additional features</th></tr></thead><tbody><tr><td><strong>Command</strong></td><td>Run an external command and stream it's output.</td><td><ul><li>Automatic command install.</li><li>Priviledged mode (<code>sudo</code>).</li></ul></td></tr><tr><td><strong>Task</strong></td><td>Run a task.</td><td><ul><li>Remote mode (Celery).</li><li>Chunking on big inputs.</li><li>Direct calling from library.</li></ul></td></tr><tr><td><strong>Workflow</strong></td><td>Run a DAG of tasks, defined in a YAML config file.</td><td><ul><li>Remote mode (Celery).</li><li>Distributed (Celery).</li><li>Task chaining and parallel.</li><li>Re-use previous results as task inputs.</li></ul></td></tr><tr><td><strong>Scan</strong></td><td>Run a DAG of workflows, defined in a YAML config file.</td><td><ul><li>Distributed (Celery).</li><li>Workflow chaining.</li><li>Re-use previous results as workflow inputs.</li></ul></td></tr></tbody></table>

***

## Lifecyle hooks

Here is an overview of how a runner's lifecycle:

<figure><img src="https://3064929067-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F787L9q3BUjAZ71WVY90V%2Fuploads%2FUt1p63MOq70KtHCs2Vyn%2FFlowchart.png?alt=media&amp;token=1f6b8612-b2df-4234-adba-672b74466644" alt=""><figcaption><p>Runner lifecycle</p></figcaption></figure>

The `Runner` lifecycle contains hooks that a user can plug into:

**Base hooks:**

* `before_init`: executed before the base runner's init starts.
* `on_build`: executed when a task is built for dispatch to a Celery worker, before it runs.
* `on_init`: executed when the base runner's init is completed.
* `on_start` : executed when the runner has started running.
* `on_interval`: executed after each item is processed (drivers use it to push periodic runner updates).
* `on_end` : executed when the runner has finished running.
* `on_cmd`: runs when the mapped command is built <mark style="color:red;">**\[**</mark><mark style="color:red;">**&#x20;**</mark><mark style="color:red;">**`Command`**</mark><mark style="color:red;">**&#x20;**</mark><mark style="color:red;">**runner only ]**</mark>.
* `on_cmd_opts`: runs when the command options are built, to adjust them <mark style="color:red;">**\[**</mark><mark style="color:red;">**&#x20;**</mark><mark style="color:red;">**`Command`**</mark><mark style="color:red;">**&#x20;**</mark><mark style="color:red;">**runner only ]**</mark>.
* `on_cmd_done`: runs when the command has finished running <mark style="color:red;">**\[**</mark><mark style="color:red;">**&#x20;**</mark><mark style="color:red;">**`Command`**</mark><mark style="color:red;">**&#x20;**</mark><mark style="color:red;">**runner only ]**</mark>.
* `on_line`: executed when a line is output to stdout or stderr <mark style="color:red;">**\[**</mark><mark style="color:red;">**&#x20;**</mark><mark style="color:red;">**`Command`**</mark><mark style="color:red;">**&#x20;**</mark><mark style="color:red;">**runner only ]**</mark>.

**Item hooks:**

* `on_item_pre_convert`: executed before an item is converted to an output type.
* `on_item`: executed when the runner emits an item.
* `on_duplicate`: runs after an item has been marked as a duplicate.
* `on_line`: executed when a line is emitted to `stdout` or `stderr` <mark style="color:red;">**\[**</mark><mark style="color:red;">**&#x20;**</mark><mark style="color:red;">**`Command`**</mark><mark style="color:red;">**&#x20;**</mark><mark style="color:red;">**runner only ]**</mark>.
* `on_error`: executed when an error is emitted by the command <mark style="color:red;">**\[**</mark><mark style="color:red;">**&#x20;**</mark><mark style="color:red;">**`Command`**</mark><mark style="color:red;">**&#x20;**</mark><mark style="color:red;">**runner only ]**</mark> .
* `on_`<mark style="color:purple;">`{serializer}`</mark>`_loaded`: executed when a serializer has finished running. For instance, `on_json_loaded` after the `JSONSerializer` has finished running <mark style="color:red;">**\[**</mark><mark style="color:red;">**&#x20;**</mark><mark style="color:red;">**`Command`**</mark><mark style="color:red;">**&#x20;**</mark><mark style="color:red;">**runner only ]**</mark> .

{% hint style="info" %}
**All hooks** are defined with the `@staticmethod` decorator and take **`self`** as the **first** argument so that you can use the runner data in your hook implementation.\
\
**Item hooks** take **`item`** as the **second** argument and expect you to return the modified item.
{% endhint %}

***

## Using hooks

There are two different ways of specifying hooks: **static hooks** (in the task definition class), **dynamic hooks** (passed to a runner at runtime), or **drivers (collection of hooks).**

### Static hooks

Static hooks **are** specified in the task specification class as `staticmethod`s:

```python
from secator.runners import Command
from secator.decorators import task
from mylib import send_to_aws_s3


@task()
class mytool(Command):
    # ...

    @staticmethod
    def on_item(self, item):
        if item._type == 'url':
            send_to_aws_s3(item.stored_response_path)
        return item
```

### Dynamic hooks

Dynamic hooks are specified at runtime by passing them to a runner.

{% hint style="info" %}
Dynamic hooks are a **library-only** feature, they are not available in the CLI.
{% endhint %}

Here are examples of specifying dynamic hooks:

{% tabs %}
{% tab title="Task" %}

<pre class="language-python"><code class="lang-python">from secator.task import mytool

api_url = 'https://myapi.com'
hooks = {
    'on_item': lambda self, item: requests.post(api_url, json=item.toDict())
}
<strong>mytool('TARGET', hooks=hooks).run()
</strong></code></pre>

{% endtab %}

{% tab title="Workflow / Scan" %}

```python
from secator.runners import Workflow, Task
from secator.template import TemplateLoader
from secator.hooks.mongodb import update_runner, update_finding

config = TemplateLoader(path='/path/to/my/workflow.yaml')
workflow = Workflow(
    config,
    hooks={
       Workflow: {
            'on_init': [update_runner],
            'on_start': [update_runner],
            'on_iter': [update_runner],
            'on_end': [update_runner]
        },
        Task: {
            'on_init': [update_runner],
            'on_item': [update_finding],
            'on_duplicate': [update_finding],
            'on_iter': [update_runner],
            'on_end': [update_runner]
        }
    }
)
workflow.run()
```

{% endtab %}
{% endtabs %}

### **Drivers**

See [Drivers](/in-depth/concepts/drivers).

***


# Drivers

... or how to route live results to a destination.

A driver is a set of hooks that constitute a full integration to route live results to certain destination.

Unlike hooks, you can use a driver from the CLI by using the `-driver` option. Several drivers can be combined with commas:

```bash
secator w host_recon example.com -driver mongodb,discord
```

To use drivers on every run, set them as defaults:

```bash
secator config set drivers.defaults mongodb
```

Available drivers:

| Driver    | Destination                                   | Addon                            | Queryable             |
| --------- | --------------------------------------------- | -------------------------------- | --------------------- |
| `json`    | `report.json` file of each run (default)      | none                             | yes (`local` backend) |
| `mongodb` | MongoDB database                              | `secator install addons mongodb` | yes                   |
| `sqlite`  | SQLite database file                          | none                             | yes                   |
| `api`     | Any HTTP API implementing the expected routes | none                             | yes                   |
| `gcs`     | Google Cloud Storage (files only)             | `secator install addons gcs`     | no                    |
| `discord` | Discord channel (notifications)               | none                             | no                    |

"Queryable" drivers are also **query backends**: `secator q`, `secator r` and `secator ws` can read results back from them with `--driver` (see [Querying results & reports](/getting-started/querying-results)).

{% hint style="info" %}
When no database driver (`mongodb`, `sqlite` or `api`) is active, the `json` driver is enabled automatically, so every run is queryable. Pass `-driver local` to ignore the configured `drivers.defaults` for a single run.
{% endhint %}

***

## JSON driver

The `json` driver writes each runner's `report.json` (in its report folder) live, as results come in and as the runner status changes. Results can be queried while the run is still in progress:

```bash
secator w host_recon example.com -driver json
secator q "port" --driver local
```

This driver is enabled by default when no other database driver is active.

***

## MongoDB driver

To export live results to MongoDB database:

<pre class="language-bash"><code class="lang-bash"><strong>secator install addons mongodb
</strong>secator config set addons.mongodb.url mongodb://localhost
secator w host_recon example.com -driver mongodb
</code></pre>

Results will be added to the database in real-time as results come through from the various tools supported by `secator`.

Runners are stored in the `tasks`, `workflows` and `scans` collections, and results (see [Output types](/in-depth/concepts/output-types)) in the `findings` collection. Duplicate findings across runs of the same workspace are tagged automatically.

***

## SQLite driver

To store live results in a local SQLite database (no server needed):

```bash
secator w host_recon example.com -driver sqlite
secator q "vulnerability" --driver sqlite
```

The database file defaults to `~/.secator/secator.db`; change it with `addons.sqlite.path`. Runners are stored in the `tasks`, `workflows` and `scans` tables, and results in the `findings` table.

***

## API driver

The `api` driver sends runners and findings to an HTTP API as they are produced, and can query them back. It is meant to integrate `secator` with your own application:

```bash
secator config set addons.api.url https://myapp.example.com/api
secator config set addons.api.key <API_KEY>
secator w host_recon example.com -driver api
```

Requests are JSON, authenticated with an `Authorization: <header_name> <key>` header (`Bearer` by default). The driver uses the following routes, relative to `addons.api.url` (each one configurable with the matching `addons.api.*_endpoint` key):

| Action                   | Method     | Default route          |
| ------------------------ | ---------- | ---------------------- |
| Create a runner          | POST       | `runners`              |
| Update a runner          | PUT        | `runner/{runner_id}`   |
| Create a finding         | POST       | `findings`             |
| Update a finding         | PUT        | `finding/{finding_id}` |
| Search findings          | POST       | `findings/_search`     |
| List / create workspaces | GET / POST | `workspaces`           |

See the `addons.api` section of the [Configuration reference](/getting-started/configuration/configuration-reference) for all routes and options.

{% hint style="info" %}
A common use of the `api` driver is to run `secator` on a client that can reach an internal target and have it POST runners/findings to a `secator-api` endpoint, so an externally-run scan still shows up on the platform. See [Running internal scans and getting results on the platform](/in-depth/running-internal-scans-on-the-platform) for the full walkthrough.
{% endhint %}

***

## GCS driver

To upload files (screenshots and stored responses) from URL results to Google Cloud Storage:

<pre class="language-bash"><code class="lang-bash"><strong>secator install addons gcs
</strong>secator config set addons.gcs.bucket_name my-bucket-name
secator w host_recon example.com -driver gcs
</code></pre>

Files will be uploaded to Google Cloud Storage in real-time as results come through. The local file paths (`screenshot_path`, `stored_response_path`) in URL results will be replaced with GCS URLs (`gs://bucket-name/blob-name`).

{% hint style="info" %}
**Authentication**: The GCS driver uses the Google Cloud Storage client library which automatically detects credentials. You can authenticate using one of the following methods:

* Set the `GOOGLE_APPLICATION_CREDENTIALS` environment variable to point to your credentials JSON file
* Use Application Default Credentials if running on Google Cloud Platform
* The client library will automatically detect credentials from your environment

The `addons.gcs.credentials_path` config option is available but optional, as the client library handles credential detection automatically.
{% endhint %}

{% hint style="info" %}
The `gcs` driver always runs before database drivers, so stored results already contain the GCS URLs.
{% endhint %}

***

## Discord driver

To send runner updates and findings to a Discord channel:

```bash
secator config set addons.discord.webhook_url https://discord.com/api/webhooks/<ID>/<TOKEN>
secator config set addons.discord.min_severity medium
secator w host_recon example.com -driver discord
```

By default, only `vulnerability` findings of severity `high` or above are sent (see `addons.discord.finding_types` and `addons.discord.min_severity`). If you also set `addons.discord.bot_token`, each runner gets its own thread, and its status message is edited in place.

***

## Custom drivers

You can write your own driver: drop a Python file defining a `HOOKS` dict in your templates folder (`~/.secator/templates/`). The file name becomes the driver name:

```python
# ~/.secator/templates/printer.py
from secator.runners import Task, Workflow, Scan


def on_item(self, item):
    print(f'[{self.name}] new {item._type}')
    return item


HOOKS = {
    Task: {'on_item': [on_item]},
    Workflow: {'on_item': [on_item]},
    Scan: {'on_item': [on_item]},
}
```

```bash
secator x httpx example.com -driver printer
```

{% hint style="info" %}
See [Runners](/in-depth/concepts/runners#lifecyle-hooks) for the list of available hooks.
{% endhint %}

***


# Profiles

... or how to manage different runner option sets effectively.

`secator` profiles are a way to mutualize sets of options to be quickly re-used in tasks, workflows, and scans. Profiles allow you to apply predefined option configurations with a single flag, making it easy to switch between different scanning strategies.

***

## Using profiles

You can use profiles with any task, workflow, or scan by using the `-pf` or `--profiles` flag:

{% tabs %}
{% tab title="Task" %}

```bash
secator x httpx example.com -pf aggressive,full
```

{% endtab %}

{% tab title="Workflow" %}

```bash
secator w host_recon example.com -pf aggressive,full
```

{% endtab %}

{% tab title="Scan" %}

```bash
secator s host example.com -pf aggressive,full
```

{% endtab %}
{% endtabs %}

You can combine multiple profiles by separating them with commas. Options from profiles are merged, with later profiles taking precedence over earlier ones.

***

## Listing available profiles

To see all available profiles and their options:

```bash
secator p list
```

***

## Built-in profiles

`secator` comes with several built-in profiles organized by category:

### Speed profiles

These profiles control the aggressiveness and speed of scans by adjusting rate limits, delays, timeouts, and retries.

#### Aggressive (`aggressive`)

Optimized for internal networks or time-sensitive scans with no rate limiting.

```yaml
rate_limit: 10000
delay: 0
timeout: 1
retries: 1
```

**Use case**: Fast scanning on trusted internal networks where you need results quickly.

#### Insane (`insane`)

Maximum speed for local LAN scanning or stress testing.

```yaml
rate_limit: 100000
delay: 0
timeout: 1
retries: 0
```

**Use case**: Local network scanning or stress testing where network impact is not a concern.

#### Paranoid (`paranoid`)

Maximum stealth with very conservative settings.

```yaml
rate_limit: 5
delay: 5
timeout: 15
retries: 5
```

**Use case**: Scanning sensitive networks where stealth is critical and you can afford to wait.

#### Polite (`polite`)

Balanced settings to avoid overloading the network.

```yaml
rate_limit: 100
delay: 0
timeout: 10
retries: 5
```

**Use case**: General-purpose scanning where you want to be respectful of network resources.

***

### Evasion profiles

These profiles help evade detection systems and maintain anonymity.

#### Sneaky (`sneaky`)

IDS/IPS evasion for sensitive networks using packet fragmentation.

```yaml
fragment: true
nmap_light_fragment: true
```

**Use case**: Scanning networks with active IDS/IPS systems that you want to evade.

#### Stealth (`stealth`)

Stealth scan using TCP SYN scanning.

```yaml
tcp_syn_stealth: true
nmap_light_tcp_syn_stealth: true
scan_type: s
```

**Use case**: Port scanning where you want to minimize detection by using stealth techniques.

#### Tor (`tor`)

Anonymous scanning using the Tor network.

```yaml
proxy: auto
```

**Use case**: Scanning where you need anonymity and want to route traffic through Tor.

{% hint style="info" %}
Make sure you have Tor configured and running, and set up your proxy settings in the config (see [Proxies](/in-depth/concepts/proxies)).
{% endhint %}

***

### General profiles

These profiles control the overall scanning approach and feature activation.

#### Active (`active`)

Active scanning only - no passive sources are used. This profile enforces active-only mode.

```yaml
active: true
domain_recon_active: true
subdomain_recon_active: true
host_recon_active: true
url_crawl_active: true
url_vuln_active: true
```

**Use case**: When you want to ensure all scanning is done actively (making requests to targets) and avoid passive data sources.

#### Passive (`passive`)

Passive scanning only - no requests are made to targets. This profile enforces passive-only mode.

```yaml
passive: true
domain_recon_passive: true
subdomain_recon_passive: true
host_recon_passive: true
url_crawl_passive: true
url_vuln_passive: true
```

**Use case**: When you want to gather information without making any requests to the target (e.g., using only OSINT sources).

#### Full (`full`)

Activates all optional features for comprehensive scanning.

```yaml
# Task options
headless: true
system_chrome: true
no_sandbox: true
screenshot: true
juicy_extensions: 3
server_defaults: false

# Workflow options
ports: "-"
nuclei: true
brute_dns: true
brute_http: true
hunt_secrets: true
test_ssl: true

# Scan options
host_recon_nuclei: true
host_recon_nmap_ports: "-"
domain_recon_testssl_server_defaults: null
subdomain_recon_hunt_secrets: true
subdomain_recon_test_ssl: true
subdomain_recon_testssl_server_defaults: null
url_crawl_hunt_secrets: true
url_vuln_nuclei: true
url_crawl_cariddi_juicy_extensions: 3
```

**Use case**: Comprehensive security assessments where you want to enable all available features including headless browsing, screenshots, vulnerability scanning, secret hunting, and SSL testing.

#### Hunt secrets (`hunt_secrets`)

Hunt for secrets and other interesting content in HTTP responses, in every workflow that supports it.

```yaml
hunt_secrets: true
subdomain_recon_hunt_secrets: true
url_crawl_hunt_secrets: true
url_dirsearch_hunt_secrets: true
url_fuzz_hunt_secrets: true
url_params_fuzz_hunt_secrets: true
```

**Use case**: Looking for leaked API keys, tokens and credentials in crawled or fuzzed responses.

***

### Network profiles

These profiles configure network-specific scanning options.

#### All Ports (`all_ports`)

Scans all ports instead of just common ones.

```yaml
ports: "-"
host_recon_nmap_ports: "-"
```

**Use case**: When you need a complete port scan of all 65535 ports (warning: this can be very slow).

#### HTTP Headless (`http_headless`)

Enables headless browser mode for HTTP tasks.

```yaml
headless: true
system_chrome: true
no_sandbox: true
```

**Use case**: When you need to render JavaScript or interact with modern web applications that require a browser.

#### HTTP Record (`http_record`)

Records HTTP requests/responses and takes screenshots.

```yaml
screenshot: true
store_responses: true
system_chrome: true
no_sandbox: true
```

**Use case**: When you need to capture full HTTP interactions and visual evidence of web pages.

***

## Profile enforcement

Some profiles have the `enforce: true` setting, which means they override any conflicting options you might pass directly. Profiles with enforcement are:

* `active`
* `passive`
* `full`
* `all_ports`
* `hunt_secrets`

When using enforced profiles, the profile options take precedence over command-line options.

***

## Combining profiles

You can combine multiple profiles to create a custom configuration:

```bash
# Combine speed and evasion profiles
secator w host_recon example.com -pf aggressive,stealth

# Combine feature activation with speed
secator s host example.com -pf full,polite

# Combine network and evasion
secator x naabu example.com -pf all_ports,tor
```

When combining profiles, options are merged with later profiles taking precedence over earlier ones.

***

## Setting default profiles

You can set default profiles in your configuration that will be applied automatically:

```bash
secator config set profiles.defaults aggressive,polite
```

Default profiles are added to every run, together with the profiles passed with `-pf`.

You can also set default profiles for a single workspace (see [Workspaces](/in-depth/concepts/workspaces)):

```bash
secator config set workspaces.profiles.acme passive,polite
```

***

## Creating custom profiles

To create your own profile, create a YAML file in `~/.secator/templates/profiles/`:

```yaml
type: profile
name: my_custom_profile
category: general
description: "My custom profile description"
opts:
    rate_limit: 500
    delay: 0.5
    timeout: 5
    retries: 3
```

After creating the profile file, it will be automatically available for use with the `-pf` flag.

***

### Workspace, drivers and exporters

Besides `opts`, a profile can set the workspace, drivers and exporters of the runs it is applied to:

```yaml
type: profile
name: acme
description: "ACME engagement defaults"
workspace: acme          # run in the 'acme' workspace (unless -ws is passed, see Workspaces)
drivers: [sqlite]        # also send results to the sqlite driver
exporters: [csv, json]   # also export csv and json reports
opts:
    rate_limit: 50
```

```bash
secator s host www.acme.com -pf acme
```

How these keys merge with your own options:

| Key         | Non-enforced profile                 | Enforced profile (`enforce: true`) |
| ----------- | ------------------------------------ | ---------------------------------- |
| `opts`      | Options you pass explicitly win.     | Profile options win.               |
| `workspace` | Used only if you did not pass `-ws`. | Always used.                       |
| `drivers`   | Added to your drivers.               | Added to your drivers.             |
| `exporters` | Added to your exporters.             | Replace your exporters.            |

***

## Using profiles from Python

Pass profile names, or `TemplateLoader` objects for profiles defined on the fly, with the `profiles` kwarg:

```python
from secator.template import TemplateLoader
from secator.workflows import host_recon

quick = TemplateLoader(input={'type': 'profile', 'name': 'quick', 'opts': {'rate_limit': 1000, 'timeout': 2}})
workflow = host_recon('example.com', profiles=['polite', quick])
workflow.run()
```

***


# Workspaces

... or how to keep each engagement's results separate.

A **workspace** is a named container for reports and findings. Every task, workflow and scan runs in a workspace, and every query reads from one. Use one workspace per engagement, client or project to keep their results apart.

When you don't choose one, `secator` uses the `default` workspace.

***

## How workspaces are stored

With the default `local` backend, each workspace is a folder in your reports directory:

```
~/.secator/reports/
├── default/
│   ├── tasks/
│   ├── workflows/
│   └── scans/
└── mydomain/
    ├── tasks/
    ├── workflows/
    └── scans/
```

See [Querying results & reports](/getting-started/querying-results) for the full report layout.

With the `mongodb`, `sqlite` or `api` [Drivers](/in-depth/concepts/drivers), the workspace is saved with each runner and finding, so queries on these backends are also scoped to a workspace.

***

## Running in a workspace

Pass `-ws` to any task, workflow or scan to run it in a workspace. If the workspace doesn't exist yet, it's created:

{% tabs %}
{% tab title="Task" %}

```bash
secator x httpx mydomain.com -ws mydomain
```

{% endtab %}

{% tab title="Workflow" %}

```bash
secator w host_recon mydomain.com -ws mydomain
```

{% endtab %}

{% tab title="Scan" %}

```bash
secator s domain mydomain.com -ws mydomain
```

{% endtab %}
{% endtabs %}

***

## Managing workspaces

The `secator workspace` command (or `secator ws`) manages workspaces.

### Switching the current workspace

`secator ws use <NAME>` sets the **current** workspace. Runs without `-ws`, and commands like `secator q` and `secator r show` without `-w`, then use it:

```bash
secator ws use mydomain
secator s domain mydomain.com      # runs in 'mydomain'
secator q vulnerability            # queries 'mydomain'
```

The workspace folder is created if it doesn't exist. `secator ws create <NAME>` is an alias of `use`.

The current workspace is stored in the `workspaces.current` config key, so this also works:

```bash
secator config set workspaces.current mydomain
```

{% hint style="info" %}
With the `api` driver (passed with `--driver api` or set in `drivers.defaults`), `secator ws use` also creates the workspace on the remote API if it doesn't exist.
{% endhint %}

### Showing the current workspace

```bash
secator ws current
```

### Listing workspaces

```bash
secator ws list
```

With the `local` backend, the table shows each workspace's name, number of runs and folder. With the `mongodb`, `sqlite` or `api` backend (`--driver` or `drivers.defaults`), it shows the name, id, description, run count and findings count.

### Deleting a workspace

`secator ws rm <NAME>` deletes a workspace and **all** its reports. It shows what will be deleted and asks for confirmation. Use `-y` / `--yes` to skip the prompt:

```bash
secator ws rm old-engagement
secator ws rm old-engagement -y --driver mongodb
```

With the `local` backend, the workspace folder is removed. With the `mongodb` or `sqlite` backend, the workspace's runners and findings are also deleted from the database. With the `api` backend, a delete request is sent to the API.

`secator ws remove` and `secator ws delete` are aliases of `rm`.

***

## Summarizing a workspace

`secator ws summary` prints an overview of the findings in a workspace:

```bash
secator ws summary                 # current workspace
secator ws summary -w mydomain
```

The built-in summary shows:

* the top ports, hosts (IPs), subdomains, technologies and URLs,
* unique vulnerabilities by severity and by status,
* the top vulnerabilities, ranked by number of targets hit.

Use `--driver` to read from another backend, e.g. `secator ws summary -w mydomain --driver mongodb`.

### Custom summary templates

The summary is a [Jinja2](https://jinja.palletsprojects.com/) template. Use `--template` to render your own:

```bash
secator ws summary -w mydomain --template ./my_summary.j2
```

In the template, `workspace` is the workspace name, and `query(...)` (or its alias `q(...)`) runs a query and returns its output as text, one row per line. It takes the same options as [`secator q`](/getting-started/querying-results#querying-with-secator-query):

```
query(type_or_expression, fmt=None, count=False, uniq=False, sort=None, limit=0, group=False)
```

For example:

{% code title="my\_summary.j2" %}

```jinja
[bold]Summary for {{ workspace }}[/]

[bold cyan]Top 10 open ports[/]
{{ query("port.state == 'open'", fmt='port', count=True, limit=10) }}

[bold cyan]Web servers[/]
{{ query('technology', fmt='{product}/{version}', uniq=True) }}

[bold red]Critical and high vulnerabilities[/]
{{ query('vulnerability.severity_nb < 2', group=True, fmt='{vulnerability.name}: {vulnerability.matched_at}') }}
```

{% endcode %}

You can use [Rich markup](https://rich.readthedocs.io/en/stable/markup.html) like `[bold]...[/]` for styling. A query with no results renders as `(none)`.

***

## Default profiles per workspace

You can apply [Profiles](/in-depth/concepts/profiles) automatically to every run in a given workspace with `workspaces.profiles.<workspace>`:

```bash
secator config set workspaces.profiles.acme-external passive,polite   # set the profiles
secator config set workspaces.profiles.acme-external stealth --append # add one
secator config unset workspaces.profiles.acme-external                # remove them
```

These profiles are added to the global `profiles.defaults` and to any profiles passed with `-pf`.

Here, every run in the `acme-external` workspace is passive and polite:

```bash
secator s domain example.com -ws acme-external
```

***

## Setting the workspace in a profile

A custom profile can also choose the workspace, with the `workspace` key:

{% code title="\~/.secator/templates/profiles/acme.yaml" %}

```yaml
type: profile
name: acme
description: "ACME engagement"
workspace: acme
opts:
  rate_limit: 50
```

{% endcode %}

```bash
secator s host www.acme.com -pf acme    # runs in 'acme'
```

The run's report files are written under the profile's workspace, and that workspace's `workspaces.profiles` are applied. A `workspace` set in a profile that is itself listed in `workspaces.profiles` is ignored.

***

## How the workspace is chosen

When a run starts, its workspace is picked in this order:

1. The `workspace` of an **enforced** profile (`enforce: true`).
2. The `-ws` option.
3. The `workspace` of a non-enforced profile.
4. The current workspace (`workspaces.current`, set with `secator ws use`).
5. `default`.

{% hint style="warning" %}
`workspaces.routes` (automatic routing by target pattern) is **deprecated** and no longer has any effect. A config that still sets it loads normally: the key is ignored and secator prints a one-time deprecation warning. Use `-ws`, `secator ws use` or a profile `workspace` instead.
{% endhint %}

***

## Example: one workspace per engagement

```bash
# Start a new engagement
secator ws use acme
secator config set workspaces.profiles.acme polite

# Run recon (saved to the 'acme' workspace)
secator s domain acme.com
secator s host 10.10.0.0/24

# Review the results
secator r list -w acme
secator ws summary
secator q "vulnerability.severity in [critical, high]" -f "{vulnerability.matched_at}"

# Clean up when the engagement is over
secator ws rm acme
```


# Running internal scans on the platform

... or how to scan targets that aren't reachable from the platform, and still see the runners and findings show up there.

The Secator platform (Secator Cloud, or a self-hosted deployment of `secator-api`/`secator-ui`) dispatches and executes scans through its own worker fleet. That fleet can only reach what it has network access to — so an internal/private target (an RFC1918 range, a VPN-only host, an on-prem service) usually isn't reachable from it.

This page covers two ways to scan those targets from **outside** the platform while still getting the runners and findings to show up **on** the platform:

* [**Method A — the `api` driver**](#method-a-the-api-driver): run `secator` wherever it *can* reach the target (your laptop, a jump box, an on-prem VM), and have it POST runners/findings straight to the platform's API. Works for **both Secator Cloud and self-hosted** deployments.
* [**Method B — custom queue prefixes**](#method-b-custom-queue-prefixes-self-hosted-only): for a **self-hosted** deployment, deploy a dedicated worker that sits on your internal network (so it already has a path to the target) and route specific scans to it. Not available on multi-tenant Secator Cloud, since it requires direct access to the deployment's Celery broker.

***

## Method A — the `api` driver

This is the same mechanism the platform's own workers already use to persist runners and findings — you're just running it from a client instead of from inside the platform. See [Drivers](/in-depth/concepts/drivers) for the general concept.

### Prerequisite: network access to the platform's API

The machine running `secator` needs outbound HTTP(S) access to the platform's API endpoint:

* Secator Cloud: `https://app.secator.cloud/api` (the default, no config needed).
* Self-hosted: your deployment's `secator-api` URL.

If the runner sits behind a restrictive firewall/proxy, add an allow-rule (or point it at an egress proxy) for that one endpoint — `secator`'s HTTP client is a standard `requests` session, so it honors the usual `HTTPS_PROXY` / `NO_PROXY` environment variables.

### Step 1: point `secator` at the platform API

```bash
# Only needed for a self-hosted deployment — Secator Cloud is the default.
secator config set addons.api.url https://<your-secator-api-host>/api

# API token (Workspace or account settings on the platform -> API tokens).
secator config set addons.api.key <API_TOKEN>
```

Both can also be set via environment variables, which is usually more convenient on a disposable scanning box:

```bash
export SECATOR_ADDONS_API_URL=https://<your-secator-api-host>/api
export SECATOR_ADDONS_API_KEY=<API_TOKEN>
```

{% hint style="info" %}
The token is sent as `Authorization: Bearer <API_TOKEN>` by default (`addons.api.header_name`, default `Bearer`).
{% endhint %}

### Step 2: pick a workspace

Findings and runners must be attached to a real workspace — the special `default` workspace cannot be used with the `api` driver. Create/select one (this calls the platform API and is idempotent, so it's safe to re-run):

```bash
secator ws use my-internal-scans -driver api
```

`ws use` sets this as the **current workspace** (persisted to your config), so every subsequent run and query uses it automatically — you don't need to pass `-ws` each time. Pass `-ws <name>` on a single command only to override the current workspace for that run.

### Step 3: run with `-driver api`

```bash
secator w host_recon 10.10.0.0/24 -driver api
```

Since `my-internal-scans` is already the current workspace from Step 2, no `-ws` is needed here. If you skipped Step 2 (or want a different workspace for this run), add it explicitly: `secator w host_recon 10.10.0.0/24 -driver api -ws my-internal-scans`.

`-driver` is comma-separated, so you can combine it with another driver, e.g. also keep a local MongoDB copy: `-driver api,mongodb`.

As the scan runs, `secator` POSTs the runner (on start, on each progress update, on completion) and every finding to the platform's API — the run shows up live on the platform under the workspace you picked, exactly like a scan dispatched from the platform itself.

***

## Method B — custom queue prefixes (self-hosted only)

Method A gets results from an external scan *into* the platform. Custom queue prefixes solve a different problem: routing a scan *dispatched from the platform* to a specific, dedicated pool of workers — for example a worker you run on an internal network segment, so it can already reach targets the shared worker fleet can't.

This requires direct connectivity between the platform's Celery broker and your dedicated worker, which is only possible on a self-hosted deployment.

### Step 1: deploy a worker on a prefixed queue set

```bash
secator worker --queue-prefix fleet1
# short flag: secator worker -qp fleet1
```

A prefix of `fleet1` makes the worker subscribe to `fleet1-small`, `fleet1-medium`, `fleet1-large`, `fleet1-extra_large`, `fleet1-poll`, `fleet1-celery`, `fleet1-results` and `fleet1-mongodb` — the classic queue set, prefixed. You can set the prefix with the flag, or deployment-wide via `SECATOR_CELERY_QUEUE_PREFIX=fleet1` (equivalently `CONFIG.celery.queue_prefix`).

{% hint style="warning" %}
**mongodb/default-queue caveat**: the mongodb hook and other statically-routed tasks are always routed using the **deployment-level** `CONFIG.celery.queue_prefix` — never the per-run prefix below. So a dedicated worker pool should also set `SECATOR_CELERY_QUEUE_PREFIX=fleet1` on itself, otherwise its mongodb/default queue won't match what it's actually subscribed to.
{% endhint %}

### Step 2: allow clients to choose a prefix

Custom queue-prefix routing is off by default — any client-supplied prefix is silently dropped. On the platform's API, enable it with:

```bash
ALLOW_CUSTOM_QUEUE_PREFIX=1
```

Once set, `GET /capabilities` reports `{"custom_queue_prefix": true}`, and the platform's UI shows a "Queue prefix" field on the run-creation form.

### Step 3: run a scan on that pool

* **CLI:** `secator w host_recon 10.10.0.0/24 --queue-prefix fleet1` (short: `-qp fleet1`)
* **Library:** `run_opts["queue_prefix"] = "fleet1"`
* **Platform UI:** fill in the "Queue prefix" field (only visible once Step 2 is enabled).

If no prefix is given, `CONFIG.celery.queue_prefix` is used as a deployment default; if that's also unset, the run goes to the normal, unprefixed queues. Precedence is: **run option → deployment config → none**.

A prefix must match `^[a-z0-9][a-z0-9_-]{0,31}$` (lowercase alphanumeric, `-`/`_`, starting with a letter or digit, 32 chars max) — anything else is rejected.

### End-to-end example

```bash
# 1. On a box with access to your internal network, start a dedicated worker:
SECATOR_CELERY_QUEUE_PREFIX=fleet1 secator worker --queue-prefix fleet1

# 2. On the platform's API, allow clients to pick a prefix:
export ALLOW_CUSTOM_QUEUE_PREFIX=1

# 3. Dispatch a scan onto that pool (from the CLI, the library, or the UI):
secator w host_recon 10.10.0.0/24 --queue-prefix fleet1 -ws my-internal-scans
```

The scan is picked up only by workers listening on `fleet1-*`, and the runner

* findings land on the platform under `my-internal-scans` like any other run.

***

## Combining both methods

The two methods are independent and can be combined: a self-hosted deployment can route a scan to a dedicated internal worker pool (Method B) whose mongodb/findings writes already land in the platform's own database — no `api` driver needed there, since that worker *is* part of the platform. Method A is for the case where `secator` runs **entirely outside** the platform's Celery/Mongo infrastructure (e.g. your own laptop) and needs to phone results home over HTTP instead.


# Deployment

... or how to run secator anywhere.

Once you have started using `secator` locally, you can deploy it on remote instances.

***

## Amazon Web Services (AWS)

*Amazon Web Services* (AWS) is a subsidiary of Amazon that provides on-demand cloud computing platforms and APIs to individuals, companies.

### Elastic Compute Cloud

#### Deploy on a single EC2 instance

<figure><img src="https://3064929067-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F787L9q3BUjAZ71WVY90V%2Fuploads%2FxzrKO8jaGbPR2a99w0B5%2Fsecator-ec2-single.png?alt=media&amp;token=1a30d692-0ee4-487b-a23b-0b33e7542f61" alt=""><figcaption><p>Deployment on a single EC2 instance</p></figcaption></figure>

To deploy `secator` on AWS on a single EC2 instance, we will install RabbitMQ, Redis and a Celery worker running `secator` on the instance.

<details>

<summary>Step 1: Create an EC2 instance with AMI Ubuntu</summary>

* Go to the AWS Management Console
* Create an EC2 instance using the Ubuntu AMI
* Configure **Security Groups:**
  * Allow port 6379 (Redis)
  * Allow port 5672 (RabitMQ)
* SSH to your created instance

</details>

<details>

<summary>Step 2: Install RabbitMQ as a task broker</summary>

Celery needs a task broker to send tasks to remote workers.

```bash
sudo apt-get install curl gnupg apt-transport-https -y
curl -1sLf "https://keys.openpgp.org/vks/v1/by-fingerprint/0A9AF2115F4687BD29803A206B73A36E6026DFCA" | sudo gpg --dearmor | sudo tee /usr/share/keyrings/com.rabbitmq.team.gpg > /dev/null
curl -1sLf "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xf77f1eda57ebb1cc" | sudo gpg --dearmor | sudo tee /usr/share/keyrings/net.launchpad.ppa.rabbitmq.erlang.gpg > /dev/null
curl -1sLf "https://packagecloud.io/rabbitmq/rabbitmq-server/gpgkey" | sudo gpg --dearmor | sudo tee /usr/share/keyrings/io.packagecloud.rabbitmq.gpg > /dev/null
sudo apt-get update -y
sudo apt-get install -y erlang-base \
    erlang-asn1 erlang-crypto erlang-eldap erlang-ftp erlang-inets \
    erlang-mnesia erlang-os-mon erlang-parsetools erlang-public-key \
    erlang-runtime-tools erlang-snmp erlang-ssl \
    erlang-syntax-tools erlang-tftp erlang-tools erlang-xmerl
sudo apt-get install rabbitmq-server -y --fix-missing
sudo rabbitmq-plugins enable rabbitmq_management
sudo rabbitmqctl add_user secator <RABBITMQ_PASSWORD>
sudo rabbitmqctl set_user_tags secator administrator
sudo rabbitmqctl set_permissions -p / secator ".*" ".*" ".*"
```

**Make sure your replace the \<RABBITMQ\_PASSWORD> by a strong password that you generate.**

</details>

<details>

<summary>Step 3: Install Redis as a storage backend</summary>

Celery needs a storage backend to store results. `secator` uses the storage backend to print results in real-time.

```bash
sudo apt install redis-server
sudo vi /etc/redis/redis.conf
# set requirepass to <REDIS_PASSWORD>
# comment the "bind 127.0.0.1 ::1" line
# change "protected-mode" to "no"

sudo /etc/init.d/redis-server restart
```

**Make sure your replace the \<REDIS\_PASSWORD> by a strong password that you generate.**

</details>

<details>

<summary>Step 4: Deploy a secator worker</summary>

First, setup `secator`using the all-in-one bash setup script:

```
wget -O - https://raw.githubusercontent.com/freelabz/secator/main/scripts/install.sh | sh
```

Then, set the RabbitMQ and Redis connection details in `secator`'s config:

```bash
secator config set celery.broker_url amqp://secator:<RABBITMQ_PASSWORD>@localhost:5672/
secator config set celery.result_backend redis://default:<REDIS_PASSWORD>@localhost:6379/0
```

Finally, run a `secator worker`:

```
nohup secator worker > worker.log 2>&1 &  # start in background and save logs
```

</details>

<details>

<summary>Step 5: Run a task from your local machine</summary>

Let's configure the worker with RabbitMQ and Redis connection details:

```bash
secator config set celery.broker_url amqp://secator:<RABBITMQ_PASSWORD>@<EC2_PUBLIC_IP>:5672/
secator config set celery.result_backend redis://default:<REDIS_PASSWORD>@<EC2_PUBLIC_IP>:6379/0
```

Run a test task:

```
secator x httpx wikipedia.org
```

You should get an output like the following:

```bash
                         __            
   ________  _________ _/ /_____  _____
  / ___/ _ \/ ___/ __ `/ __/ __ \/ ___/
 (__  /  __/ /__/ /_/ / /_/ /_/ / /    
/____/\___/\___/\__,_/\__/\____/_/     v0.0.1

                    freelabz.com

Celery worker is alive !
╭──────── Task httpx ─────────╮
│ 📜 Description: DotMap()    │
│ 👷 Workspace: default       │
│ 🍐 Targets:                 │
│    • wikipedia.org          │
│ 📌 Options:                 │
│    • follow_redirect: False │
│    • threads: 50            │
│    • debug_resp: False      │
╰─────────────────────────────╯
[10:20:54] 🎉 Task httpx sent to Celery worker...                                                                                                                                        _base.py:614
🏆 Live results:
🔗 https://wikipedia.org [301] [301 Moved Permanently] [mw1415.eqiad.wmnet] [HSTS] [text/html] [234]
```

</details>

#### Deploy on multiple EC2 instances

If you want a more scaleable architecture, we recommend deploying RabbitMQ, Redis, and Celery workers on different EC2 instances.

The steps are exactly the same as for [#deploy-on-a-single-ec2-instance](#deploy-on-a-single-ec2-instance "mention"), expect that steps 2, 3, and **4** will each be run on **separate EC2 instance**.

You can repeat step 4 on more instances to increase the number of workers.

***

## Google Cloud Platform (GCP)

The *Google Cloud Platform* (GCP) is a suite of cloud services that offers server space on virtual machines, internal networks, VPN connections, disk storage, ...

### Google Compute Engine

#### Deploy on a single GCE instance

<figure><img src="https://3064929067-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F787L9q3BUjAZ71WVY90V%2Fuploads%2FxzrKO8jaGbPR2a99w0B5%2Fsecator-ec2-single.png?alt=media&amp;token=1a30d692-0ee4-487b-a23b-0b33e7542f61" alt=""><figcaption><p>Deployment on a single EC2 instance</p></figcaption></figure>

To deploy `secator` on a single GCE machine, we will install RabbitMQ, Redis and a Celery worker running `secator` on the instance.

<details>

<summary>Step 1: Create a GCE instance</summary>

* Go to the Google Cloud Console
* Create a GCE instance using the Debian image
* Create firewall rules in **Network** > **Firewall:**
  * Allow port 6379 (Redis)
  * Allow port 5672 (RabbitMQ)
* SSH to your created instance

</details>

<details>

<summary>Step 2: Install RabbitMQ as a task broker</summary>

Celery needs a task broker to send tasks to remote workers.

```bash
sudo apt-get install curl gnupg apt-transport-https -y
curl -1sLf "https://keys.openpgp.org/vks/v1/by-fingerprint/0A9AF2115F4687BD29803A206B73A36E6026DFCA" | sudo gpg --dearmor | sudo tee /usr/share/keyrings/com.rabbitmq.team.gpg > /dev/null
curl -1sLf "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xf77f1eda57ebb1cc" | sudo gpg --dearmor | sudo tee /usr/share/keyrings/net.launchpad.ppa.rabbitmq.erlang.gpg > /dev/null
curl -1sLf "https://packagecloud.io/rabbitmq/rabbitmq-server/gpgkey" | sudo gpg --dearmor | sudo tee /usr/share/keyrings/io.packagecloud.rabbitmq.gpg > /dev/null
sudo apt-get update -y
sudo apt-get install -y erlang-base \
    erlang-asn1 erlang-crypto erlang-eldap erlang-ftp erlang-inets \
    erlang-mnesia erlang-os-mon erlang-parsetools erlang-public-key \
    erlang-runtime-tools erlang-snmp erlang-ssl \
    erlang-syntax-tools erlang-tftp erlang-tools erlang-xmerl
sudo apt-get install rabbitmq-server -y --fix-missing
sudo rabbitmq-plugins enable rabbitmq_management
sudo rabbitmqctl add_user secator <RABBITMQ_PASSWORD>
sudo rabbitmqctl set_user_tags secator administrator
sudo rabbitmqctl set_permissions -p / secator ".*" ".*" ".*"
```

**Make sure your replace the \<RABBITMQ\_PASSWORD> by a strong password that you generate.**

</details>

<details>

<summary>Step 3: Install Redis as a storage backend</summary>

Celery needs a storage backend to store results. `secator` uses the storage backend to print results in real-time.

```bash
sudo apt install redis-server
sudo vi /etc/redis/redis.conf
# set requirepass to <REDIS_PASSWORD>
# comment the "bind 127.0.0.1 ::1" line
# change "protected-mode" to "no"

sudo /etc/init.d/redis-server restart
```

**Make sure your replace the \<REDIS\_PASSWORD> by a strong password that you generate.**

</details>

<details>

<summary>Step 4: Deploy a secator worker</summary>

First, setup `secator`using the all-in-one bash setup script:

```
wget -O - https://raw.githubusercontent.com/freelabz/secator/main/scripts/install.sh | sh
```

Then, set the RabbitMQ and Redis connection details in `secator`'s config:

```bash
secator config set celery.broker_url amqp://secator:<RABBITMQ_PASSWORD>@localhost:5672/
secator config set celery.result_backend redis://default:<REDIS_PASSWORD>@localhost:6379/0
```

Finally, run a `secator worker`:

```
nohup secator worker > worker.log 2>&1 &  # start in background and save logs
```

</details>

<details>

<summary>Step 5: Run a task from your local machine</summary>

First, set the RabbitMQ and Redis connection details in `secator`'s config:

```bash
secator config set celery.broker_url amqp://secator:<RABBITMQ_PASSWORD>@<GCE_PUBLIC_IP>:5672/
secator config set celery.result_backend redis://default:<REDIS_PASSWORD>@<GCE_PUBLIC_IP>:6379/0
```

Run a test task:

```
secator x httpx wikipedia.org
```

You should get an output like the following:

```bash
                         __            
   ________  _________ _/ /_____  _____
  / ___/ _ \/ ___/ __ `/ __/ __ \/ ___/
 (__  /  __/ /__/ /_/ / /_/ /_/ / /    
/____/\___/\___/\__,_/\__/\____/_/     v0.0.1

                    freelabz.com

Celery worker is alive !
╭──────── Task httpx ─────────╮
│ 📜 Description: DotMap()    │
│ 👷 Workspace: default       │
│ 🍐 Targets:                 │
│    • wikipedia.org          │
│ 📌 Options:                 │
│    • follow_redirect: False │
│    • threads: 50            │
│    • debug_resp: False      │
╰─────────────────────────────╯
[10:20:54] 🎉 Task httpx sent to Celery worker...                                                                                                                                        _base.py:614
🏆 Live results:
🔗 https://wikipedia.org [301] [301 Moved Permanently] [mw1415.eqiad.wmnet] [HSTS] [text/html] [234]
```

</details>

#### Deploy on multiple GCE instances

If you want a more scaleable architecture, we recommend deploying RabbitMQ, Redis, and Celery workers on different machines.

The steps are exactly the same as for the previous section, except that steps 2, 3, and **4** will each be run on **separate GCE instance**.

You can repeat step 4 on more instances to increase the number of workers.

### Google Kubernetes Engine \[WIP]

### Cloud Run \[WIP]

***

## Axiom \[WIP]

**Axiom is a dynamic infrastructure framework** to efficiently work with multi-cloud environments, build and deploy repeatable infrastructure focused on offensive and defensive security.

***

## Bare metal

### Kubernetes

A Helm chart is available in the repository: <https://github.com/freelabz/secator/tree/main/helm>.

### Docker-compose \[WIP]

***


# Development setup

... or how to setup a development environment for secator.

***

## Install a development build

To install `secator` in development mode, first make sure `pip` and `virtualenv` are installed, and run the following steps:

```bash
git clone https://github.com/freelabz/secator  # clone the repository
cd secator                                     # go to the repository folder
virtualenv .venv                               # create a virtualenv
source .venv/bin/activate                      # load the virtualenv
pip install -e .[dev]                          # install secator and dev dependencies
```

{% hint style="info" %}
Use`secator health` to verify your installation. You can install addons / tools using the `secator install` command (choose what to install based on what you want to dev on).
{% endhint %}

***

## Running tests

### Unit tests

<pre class="language-bash"><code class="lang-bash"><strong>secator test unit
</strong></code></pre>

If you want to run unit tests to test a specific task only:

```sh
secator test unit --test test_tasks --tasks <TASK_NAME>
```

***

### Integration tests

```bash
secator test integration
```

If you want to run integration tests to test a specific task only:

```sh
secator test integration --test test_tasks --tasks <TASK_NAME>
```

***

### Lint tests

```bash
secator test lint
```

***


# Writing tasks

... or how to integrate new tasks with secator.

Now that you have used `secator` for a while, you might regret that the scripts or CLI tools you use daily are not supported yet. No panic, just follow the guides to integrate them yourself !

To get started:

* Read [Integrating an external command](/for-developers/writing-tasks/integrating-an-external-command) if you plan on integrating a new external command.
* Read [Integrate custom Python code \[WIP\]](/for-developers/writing-tasks/integrate-custom-python-code-wip) if you plan on integrating embedded Python code.

***


# Integrating an external command

... or how to turn a command that you use daily into an overpowered machine.

***

### Creating a task file

Imagine we have a tool named `mytool` that we want to integrate with `secator`.

Start by creating a file named `mytool.py`:

```python
from secator.decorators import task  # required for `secator` to recognize tasks
from secator.runners import Command  # the `secator` runner to use


@task()
class mytool(Command): # make sure class name is lowercase and matches the filename.
    cmd = 'mytool'  # ... or whatever the name of your external command is.

```

{% hint style="info" %}
The `task` decorator is required for `secator` to recognize class-based definition that need to be loaded at runtime.
{% endhint %}

Move this file over to:

* `~/.secator/templates/` (or whatever your `dirs.templates` in [Configuration](/getting-started/configuration) points to)

**OR**

* `secator/tasks/` if you have a [Development setup](/for-developers/development-setup) and want to contribute your task implementation to the official `secator` repository.

***

### Adding an input flag \[optional]

If your tool requires an input flag or a list flag to take its targets, for instance:

* `mytool -u TARGET`
* `mytool -l TXT_FILE`

You need to set the `input_flag` and `file_flag` class options:

```python
from secator.decorators import task
from secator.runners import Command


@task()
class mytool(Command):
    cmd = 'mytool'
    input_flag = '-u'
    file_flag = '-l'

```

Setting these attributes allows us to run `mytool` with `secator` like:

```bash
secator x mytool TARGET    # will run mytool -u TARGET
secator x mytool TXT_FILE  # will run mytool -l TXT_FILE
```

{% hint style="warning" %}
The external command you integrate must be callable on your system's `PATH`. A file located at `/opt/tools/testssl.sh/testssl.sh`, for instance, **will not** work unless its directory is on your `PATH`.

Verify that a command is callable with:

```bash
which <command>
```

If it returns a path, the command is available to `secator`. If not, add its directory to your `PATH` (or move the binary to a directory already on it).
{% endhint %}

### Parsing a command's output

Now that you have a basic implementation working, you need to convert your command's output into structured output (JSON).

Find out what your command's output looks like and pick the corresponding guide:

* Read [Parsing JSON lines](/for-developers/writing-tasks/integrating-an-external-command/parsing-json-lines) if your tool has an option to stream JSON lines (**preferred**).
* Read [Parsing output files](/for-developers/writing-tasks/integrating-an-external-command/parsing-output-files) if your tool has an option to output to a file (e.g JSON or CSV).
* Read [Parsing raw standard output](/for-developers/writing-tasks/integrating-an-external-command/parsing-raw-standard-output) if your tools **only** outputs to `stdout` .

***

### Adding more options \[optional]

To support more options, you can use the `opt_prefix`, `opts` , `opt_key_map` and `opt_value_map` attributes.

Assuming `mytool` has the `--delay`, `--debug` and `--include-tags` options, we would support them this way:

```python
@task()
class mytool(Command):
    # ...
    opt_prefix = '--'  # default is '-'
    opts = {
        'tags': {'type': str, 'short': 't', 'help': 'Tags'},
        'delay': {'type': int, 'short': 'dbg', 'help': 'Delay'},
        'debug': {'is_flag': True, 'short': 'd', 'help': 'Debug mode'},
    }
    opt_key_map = {   # to map input options
        'tags': 'include-tags',
    }
    opt_value_map = {  # to transform options values
        'delay': lambda x: x * 1000  # convert seconds to milliseconds
    }

```

With this config, running either of:

```bash
secator x mytool --tags tag1,tag2 --debug --delay 5 TARGET  # long option format
secator x mytool -t tag1,tag2 -dbg -d 5 TARGET              # short option format
```

will result in running `mytool` like:

```bash
mytool --include-tags tag1,tag2 --debug --delay 5000 -u TARGET
```

{% hint style="warning" %}
All keys in `opts` must be **lowercase**. To expose a CLI flag that is uppercase on the underlying command, define a lowercase key in `opts` and map it to the real flag via `opt_key_map`:

```python
@task()
class mytool(Command):
    # ...
    opts = {
        'products': {'type': str, 'short': 'p', 'help': 'Products'},
    }
    opt_key_map = {
        'products': 'P',  # maps the lowercase `products` key to the `-P` flag
    }
```

{% endhint %}

***

### Adding an install command \[optional]

To support installing your tool with secator, you can set the `install_cmd` , and / or `install_github_handle` attributes:

```python
@task()
class mytool(Command):
    # ...
    install_cmd = "sudo apt install -y mytool"
    install_github_handle = "myorg/mytool"

```

{% hint style="info" %}
If `install_github_handle` is set, `secator` will try to fetch a binary from GitHub releases specific to your platform, and fallback to `install_cmd` if it cannot find a suitable release, or if the API rate limit is reached.
{% endhint %}

Now you can install `mytool` using:

```bash
secator install tools mytool
```

***

### Using a category \[optional]

If your tool fits into one of `secator`'s built-in command categories, you can inherit from it's option set:

* `Http`: A tool that makes HTTP requests.
* `HttpCrawler`: A command that crawls URLs (subset of `Http`).
* `HttpFuzzer`: A command that fuzzes URLs (subset of `Http`).

You can inherit from these categories and map their options to your command.

{% hint style="info" %}
Categories are defined in `secator/tasks/_categories.py`
{% endhint %}

For instance, if `mytool` is an HTTP fuzzer, we would change it's implementation like:

```python
from secator.tasks._categories import HTTPFuzzer
from secator.definitions import OPT_NOT_SUPPORTED


@task()
class mytool(Command, HTTPFuzzer):
    # ...
    opt_key_map = {
        # HTTPFuzzer options mapping
        HEADER: 'header',
        DELAY: 'delay',
        DEPTH: OPT_NOT_SUPPORTED,
        FILTER_CODES: OPT_NOT_SUPPORTED,
        FILTER_REGEX: OPT_NOT_SUPPORTED,
        FILTER_SIZE: OPT_NOT_SUPPORTED,
        FILTER_WORDS: OPT_NOT_SUPPORTED,
        FOLLOW_REDIRECT: OPT_NOT_SUPPORTED,
        MATCH_CODES: OPT_NOT_SUPPORTED,
        MATCH_REGEX: OPT_NOT_SUPPORTED,
        MATCH_SIZE: OPT_NOT_SUPPORTED,
        MATCH_WORDS: OPT_NOT_SUPPORTED,
        METHOD: OPT_NOT_SUPPORTED,
        PROXY: OPT_NOT_SUPPORTED,
        RATE_LIMIT: OPT_NOT_SUPPORTED,
        RETRIES: OPT_NOT_SUPPORTED,
        THREADS: OPT_NOT_SUPPORTED,
        TIMEOUT: 'timeout',
        USER_AGENT: 'user-agent',

        # my tool specific options
        'tags': 'include-tags',      
    }
    opt_value_map = {
        'delay': lambda x: x * 1000  # convert seconds to milliseconds
    }

```

{% hint style="info" %}
Make sure you map **all** the options from the **`HTTPFuzzer`** category. If some options are not supported by your tool, mark them with `OPT_NOT_SUPPORTED`.
{% endhint %}

With this config, running:

```bash
secator x mytool --help
```

would list:

* The `meta`options in the `HTTPFuzzer` category that are supported by `mytool`.
* The options only usable by `mytool`.

For instance, running:

{% tabs %}
{% tab title="CLI" %}

```bash
secator x mytool \
  -header "Authorization: Bearer MYTOKEN" \
  -delay 1 \
  -ua "secator/0.6 (Debian)" \
  -timeout 5 \
  -t tag1,tag2 \
  TARGET
```

{% endtab %}

{% tab title="Python" %}

```python
from secator.tasks import mytool

task = mytool(
  'TARGET',
  header="Authorization: Bearer MYTOKEN",
  delay=1,
  user_agent="secator/0.6 (Debian)",
  timeout=5,
  tags='tag1,tag2'
)
for item in task:
     print(item)
```

{% endtab %}
{% endtabs %}

would result in running `mytool` like:

```bash
mytool \
  --header "Authorization: Bearer MYTOKEN" \
  --delay 1000 \
  --user-agent "secator/0.6 (Debian)" \
  --timeout 5 \
  --include-tags tag1,tag2
  -u TARGET
```

***

### Supporting proxies \[optional]

If your tool supports proxies, `secator` has first-class support for **`proxychains`**, **`HTTP`** and **`SOCKS5`** proxies, and can dynamically choose the type of proxy to use based on the following attributes:

* `proxy_socks5` : boolean indicating if your command supports `SOCKS5` proxies.
* `proxy_http` : boolean indicating if your command supports `HTTP` / `HTTPS` proxies.
* `proxychains`: boolean indicating if your command supports being run with `proxychains`.

{% hint style="warning" %}
If your proxy supports `SOCKS5`or `HTTP` proxies, make sure to have an option called **`proxy`** in your **`opts`** definition or it won't be picked up.

If your proxy supports `proxychains`, `secator` will use the local `proxychains` binary and `proxychains.conf` configuration, so make sure those are functional.
{% endhint %}

{% hint style="info" %}
Read [Proxies](/in-depth/concepts/proxies)for more details on how proxies work and how to configure them properly.
{% endhint %}

#### Example:

Assuming `mytool` does not support HTTP or SOCKS5 proxies, but works with `proxychains`, you can update your task definition like:

```python
@task()
class mytool(Command):
    # ...
    proxychains = True
    proxy_socks5 = False
    proxy_http = True
```

With the above configuration, running with `-proxy <VALUE>` would result in the following behaviour:

{% tabs %}
{% tab title="proxychains / auto" %}

```bash
secator x mytool -proxy proxychains TARGET
secator x mytool -proxy auto TARGET # auto-pick from proxychains > socks5 > http
```

becomes:

```bash
proxychains mytool -u TARGET
```

{% endtab %}

{% tab title="http" %}

```bash
secator x mytool -proxy "http://testmyproxy.com" <TARGET>
```

becomes:

```bash
mytool --use-proxy "http://testmyproxy.com" -u <TARGET>
```

{% endtab %}

{% tab title="random" %}

```bash
secator x mytool -proxy random <TARGET>
```

becomes:

```bash
mytool --use-proxy "http://30.10.23.42" -u <TARGET> # random proxy (FreeProxy)
```

{% endtab %}
{% endtabs %}

***

### Hooking onto runner lifecycle

You can hook onto any part of the runner lifecycle by override the hooks methods (read [Runners](/in-depth/concepts/runners#lifecyle-hooks) to know more).

#### Example:

{% tabs %}
{% tab title="CLI" %}

```python
@task()
class mytool(Command):
    # ...
    @staticmethod
    def on_line(self, line):
        return line.rstrip(',')  # strip trailing comma of stdout lines

    @staticmethod
    def on_item_pre_convert(self, item):
        item['extra_data'] = {
            'version': '2.0'  # add extra data to items
        }
        return item

```

{% endtab %}

{% tab title="Python" %}

```python
from secator.tasks import mytool

hooks = {
    'on_line': lambda x: x.strip(','),
    'on_item_pre_convert': lambda x: x | {'extra_data': {'version': '2.0'}},
}
task = mytool('TARGET', hooks=hooks)
for item in task:
     print(item)

```

{% endtab %}
{% endtabs %}

***

### Chunking

`secator` allows to chunk a task into multiple children tasks when the length of the input grows, or some other specific requirements (e.g: your command only takes one target at a time).

{% hint style="warning" %}
Chunking only works when [Distributed runs with Celery](/in-depth/distributed-runs-with-celery) are enabled.
{% endhint %}

You can specify the chunk size using the `input_chunk_size` attribute:

```python
@task()
class mytool(Command):
    # ...
    input_chunk_size = 10  # additional tasks will be spawned every 10 targets

```

With this config, running:

```bash
secator x mytool tasks.txt  # tasks.txt contains 20 targets
```

would result in:

```bash
mytool -l /tmp/task_0_9.txt
mytool -l /tmp/task_10_19.txt
```

{% hint style="info" %}
If `mytool` did not support file input (i.e: `file_flag` not defined in the task definition class), the above would still work with an `input_chunk_size = 1`, thus splitting into one command per target passed.
{% endhint %}

***


# Parsing JSON lines

... or how to integrate tools that output JSON lines.

If your tool outputs JSON lines, it's very easy to integrate it with `secator`.

Based on how your tool's output maps will map to `secator` output types, read:

* [#one-to-one-mapping](#one-to-one-mapping "mention") (1 JSON line = 1 `secator` output type).
* [#one-to-many](#one-to-many "mention") (1 JSON line = many`secator` output types).

***

## One-to-one mapping

**Steps:**

* Use the `JSONSerializer` item loader.
* Add an `output_map` to map your tool's output to one of secator's [Output types](/in-depth/concepts/output-types)**.**

#### **Example:**

For instance, `mytool` outputs JSON lines when we run it like:

```bash
mytool -jsonl -u mytarget.com
{"url": "https://mytarget.com/api", "status": 200, {"details": {"ct": "application/json"}}
{"url": "https://mytarget.com/api/metrics", "status": 403, "details": {}}
```

An integration of `mytool` with `secator` would look like:

{% code title="secator/tasks/mytool.py" %}

```python
from secator.decorators import task
from secator.runners import Command
from secator.output_types import Url
from secator.serializers import JSONSerializer
from secator.definitions import URL, STATUS_CODE, CONTENT_TYPE


@task()
class mytool(Command):
  input_flag = '-u'
  json_flag = '-jsonl'
  output_types = [Url]
  item_loaders = [JSONSerializer()]
  output_map = {
    Url: {
     URL: 'url',
     STATUS_CODE: 'status',
     CONTENT_TYPE: lambda x: x['details'].get('ct', '')
    }
  }

```

{% endcode %}

**Steps taken:**

* Import the desired runner (`Command`).
* Import the desired output type(s) and the field names that we want to map.
* Set `input_flag` to `mytool`'s input flag `-u`.
* Set `json_flag` to `mytool`'s JSON line flag `jsonl`.
* Set `output_types` to our desired output types \[[`Url`](/in-depth/concepts/output-types#url)].
* Set `output_map` to map `mytool`'s output fields to each [`Url`](/in-depth/concepts/output-types#url)'s fields (you need to map at least the `required=True` fields).

That's it ! You can now run `mytool` with `secator` and enjoy all the features it brings on top of it:

{% tabs %}
{% tab title="CLI" %}

```bash
secator x mytool mytarget.com
                         __            
   ________  _________ _/ /_____  _____
  / ___/ _ \/ ___/ __ `/ __/ __ \/ ___/
 (__  /  __/ /__/ /_/ / /_/ /_/ / /    
/____/\___/\___/\__,_/\__/\____/_/     v0.6.0

                        freelabz.com
mytool -u mytarget.com -jsonl
🔗 https://mytarget.com/api [200] [application/json]
🔗 https://mytarget.com/api/metrics [403]
```

{% endtab %}

{% tab title="Python" %}

```python
from secator.tasks import mytool

task = mytool('mytarget.com')
for item in task:
    print(item)  # item is now a secator output type like Vulnerability or Port

```

{% endtab %}
{% endtabs %}

***

## One-to-many

**Steps:**

* Add a static method `on_json_loaded` to hook onto the JSON Serializer output.
* Modify the data and yield `secator` [Output types](/in-depth/concepts/output-types).

#### **Example:**

For instance, if `mytool` outputs urls in batch:

```bash
mytool -jsonl -u mytarget.com
{"urls": [{"url": "https://mytarget.com/api", "status": 200, "content-type": "application/json"}, {"url": "https://mytarget.com/api/metrics", "status": 403, "content-type": "application/text"}]
```

An integration of `mytool` with `secator` would look like:

<pre class="language-python" data-title="secator/tasks/mytool.py"><code class="lang-python"><strong>from secator.decorators import task
</strong><strong>from secator.runners import Command
</strong>from secator.output_types import Url
from secator.definitions import URL, STATUS_CODE, CONTENT_TYPE


@task()
class mytool(Command):
  input_flag = '-u'
  json_flag = '-jsonl'
  output_types = [Url]

  @staticmethod
  def on_json_loaded(self, data):
      for item in data['urls']:
          yield Url(
            url=item['url'],
            status_code=item['status'],
            content_type=item.get('content-type', '')
          )

</code></pre>

{% hint style="info" %}
See [Runners](/in-depth/concepts/runners#lifecyle-hooks) for more details on which hooks you can use.
{% endhint %}

That's it ! You can now run `mytool` with `secator` and enjoy all the features it brings on top of it:

{% tabs %}
{% tab title="CLI" %}

```bash
secator x mytool mytarget.com
                         __            
   ________  _________ _/ /_____  _____
  / ___/ _ \/ ___/ __ `/ __/ __ \/ ___/
 (__  /  __/ /__/ /_/ / /_/ /_/ / /    
/____/\___/\___/\__,_/\__/\____/_/     v0.6.0

                        freelabz.com
mytool -u mytarget.com -jsonl
🔗 https://mytarget.com/api [200] [application/json]
🔗 https://mytarget.com/api/metrics [403]
```

{% endtab %}

{% tab title="Python" %}

```python
from secator.tasks import mytool

task = mytool('mytarget.com')
for item in task:
    print(item)  # item is now a secator output type like Vulnerability or Port

```

{% endtab %}
{% endtabs %}


# Parsing raw standard output

... or how to integrate tools that prints to stdout and do not support JSON lines.

If your tool does not output JSON lines / JSON files, it's requires a bit more effort to integrate it with `secator`.

Depending on how you want to parse the output, read:

* [#using-regular-expressions](#using-regular-expressions "mention")
* [#writing-a-custom-item-loader](#writing-a-custom-item-loader "mention")

***

## Using regular expressions

**Steps:**

* Use the `RegexSerializer` item loader.
* Use the `on_regex_loaded` hook to yield `secator` output types.

#### **Example:**

Assume `mytool` outputs on stdout like:

```bash
mytool -u mytarget.com
[INF] This is an info message
[ERR] This is an error message
[FOUND] https://mytarget.com/api [type=url] [status=200] [content_type=application/json] [title=MyAwesomeWebPage]
[FOUND] https://mytarget.com/api/metrics [type=url] [status=403]
[FOUND] A3TBABCD1234EFGH5678 [type=aws_api_key] [matched_at=https://mytarget/api/.aws_key.json]
[FOUND] <-- an HTML comment --> [type=aws_api_key] [matched_at=https://mytarget/api/.aws_key.json]
[FOUND] CVE-2021-44228 [type=vulnerability] [matched_at=https://mytarget/api/sensitive_api_path]
```

First we need to find a regular expression that will match the items marked with `[FOUND]` and get the individual values using a named regex (you can use [Pythex](https://pythex.org) for this).

Here is the one we came up with:

{% code overflow="wrap" %}

```python
OUTPUT_REGEX = r'\[\w+]\s(?P<value>.*)\s\[type=(?P<type>[\w_]+)\](\s\[status=(?P<status>\d+)\])?(\s\[content_type=(?P<content_type>[\w\/]+)\])?(\s\[title=(?P<title>.*)\])?(\s\[matched_at=(?P<matched_at>.*)\])?'
```

{% endcode %}

An integration of `mytool` with `secator` would look like:

{% code title="secator/tasks/mytool.py" %}

```python
from secator.decorators import task
from secator.runners import Command
from secator.output_types import Url, Tag, Vulnerability
from secator.serializers import RegexSerializer
from secator.tasks._categories import Vuln

OUTPUT_REGEX = r'\[\w+]\s(?P<value>.*)\s\[type=(?P<type>[\w_]+)\](\s\[status=(?P<status>\d+)\])?(\s\[content_type=(?P<content_type>[\w\/]+)\])?(\s\[title=(?P<title>.*)\])?(\s\[matched_at=(?P<matched_at>.*)\])?'


@task()
class mytool(Command):
  cmd = '/home/osboxes/.local/bin/mytool'
  input_flag = '-u'
  json_flag = '-jsonl'
  output_types = [Url, Tag, Vulnerability]

  # Use the RegexSerializer to load the stdout input
  item_loaders = [
    RegexSerializer(
      OUTPUT_REGEX,
      fields=['value', 'type', 'status', 'content_type', 'title', 'matched_at']
    )
  ]

  # React to items loaded by the RegexSerializer, and yield secator output types
  # like Url, Vulnerability, and Tag.
  @staticmethod
  def on_regex_loaded(self, item):
    # this is called after the regex serializer runs,
    # so we can expect item to be a dict with the matched regex values
    if (item['type'] == 'url'):
      yield Url(
        url=item['value'],
        status_code=int(item['status']),
        content_type=item['content_type'],
        title=item['title']
      )
    elif (item['type'] == 'vulnerability'):
      cve_id = item['value']
      lookup_data = Vuln.lookup_cve(cve_id)  # perform vulnerability search
      vuln = {
        'matched_at': item['matched_at']
      }
      if lookup_data:
        vuln.update(**lookup_data)
      yield Vulnerability(**vuln)
    else:
      yield Tag(
        name=item['type'],
        match=item['matched_at'],
        extra_data={
          'secret': item['value']
        }
      )

```

{% endcode %}

Run it with `secator`:

{% tabs %}
{% tab title="CLI" %}

```bash
$ secator x mytool mytarget.com

                         __            
   ________  _________ _/ /_____  _____
  / ___/ _ \/ ___/ __ `/ __/ __ \/ ___/
 (__  /  __/ /__/ /_/ / /_/ /_/ / /    
/____/\___/\___/\__,_/\__/\____/_/     v0.6.0

                        freelabz.com

No Celery worker alive.
/home/osboxes/.local/bin/mytool -u mytarget.com -jsonl
[INF] This is an info message
[ERR] This is an error message
🔗 https://mytarget.com/api [200] [MyAwesomeWebPage] [application/json]
🔗 https://mytarget.com/api/metrics [403]
🏷️ aws_api_key found @ https://mytarget/api/.aws_key.json
    secret: A3TBABCD1234EFGH5678
🚨 [Object Injection 🡕] [critical] https://mytarget/api/sensitive_api_path
```

{% endtab %}

{% tab title="Python" %}

```python
from secator.tasks import mytool
task = mytool('mytarget.com')
for item in task:
    print(item)  # this will output Url, Vulnerability, or Tag items.

```

{% endtab %}
{% endtabs %}

***

### Writing a custom item loader

**Steps:**

* Override the `item_loader` static method to parse the standard output with custom code.

**Example:**

Assume `mytool` outputs on stdout like:

```bash
mytool -u mytarget.com
https://mytarget.com/api | url | 200 | application/json | MyAwesomePage
https://mytarget.com/api/metrics | url | 403
A3TBABCD1234EFGH5678 | aws_api_key | http://mytarget/api/.aws_key.json
<-- an HTML comment --> | html_comment | http://mytarget/api/.aws_key.json
CVE-2021-44228 | vulnerability | http://mytarget/api/sensitive_ap
```

```python
from secator.decorators import task
from secator.runners import Command
from secator.output_types import Url, Tag, Vulnerability


@task()
class mytool(Command):
  cmd = '/home/osboxes/.local/bin/mytool'
  input_flag = '-u'
  json_flag = '-jsonl'
  output_types = [Url, Tag, Vulnerability]

  @staticmethod
  def item_loader(self, line):
      items = [c.strip() for c in line.split('|')]
      value, item_type = tuple(items[0:2])
      if item_type == 'url':
          yield Url(
              url=value,
              status_code=items[3],
              content_type=items[4] if len(items) > 3 else '',
              title=items[5] if len(items) > 4 else ''
          )
      elif item_type == 'vulnerability':
          cve_id = value
          lookup_data = Vuln.lookup_cve(cve_id)  # perform vulnerability search
          vuln = {
            'matched_at': items[2]
          }
          if lookup_data:
            vuln.update(**lookup_data)
          yield Vulnerability(**vuln)
      else: # tag
          yield Tag(
              name=item_type,
              match=items[2],
              extra_data={
                  'value': value
              }
          )
 
```

Run it with `secator`:

{% tabs %}
{% tab title="CLI" %}

```bash
$ secator x mytool mytarget.com

                         __            
   ________  _________ _/ /_____  _____
  / ___/ _ \/ ___/ __ `/ __/ __ \/ ___/
 (__  /  __/ /__/ /_/ / /_/ /_/ / /    
/____/\___/\___/\__,_/\__/\____/_/     v0.6.0

                        freelabz.com

No Celery worker alive.
/home/osboxes/.local/bin/mytool -u mytarget.com -jsonl
[INF] This is an info message
[ERR] This is an error message
🔗 https://mytarget.com/api [200] [MyAwesomeWebPage] [application/json]
🔗 https://mytarget.com/api/metrics [403]
🏷️ aws_api_key found @ https://mytarget/api/.aws_key.json
    secret: A3TBABCD1234EFGH5678
🚨 [Object Injection 🡕] [critical] https://mytarget/api/sensitive_api_path
```

{% endtab %}

{% tab title="Python" %}

```python
from secator.tasks import mytool
task = mytool('mytarget.com')
for item in task:
    print(item)  # this will output Url, Vulnerability, or Tag items.

```

{% endtab %}
{% endtabs %}

***


# Parsing output files

... or how to integrate tools that save their output to a file.

If your tool supports output as individual files, you can easily integrate it with `secator`.

* If you can set the output file path in advance, read [#setting-the-file-path](#setting-the-file-path "mention").
* If you cannot know, set, or guess the output file path in advance, read [#using-a-regular-expression-to-get-the-file-path](#using-a-regular-expression-to-get-the-file-path "mention").

***

## Setting the file path

**Steps:**

* Set the file path before the command runs using the `on_init` hook
* Use the `on_cmd_done` hook to read the file and yield `secator` output types.

#### **Example:**

Assume `mytool` outputs to a JSON file like:

```bash
mytool -u mytarget.com -format json -o result.json
...
```

and the corresponding JSON would look like:

```json
[
  { "type": "url", "url": "http://mytarget.com", "status": 200 },
  { "type": "vulnerability", "target": "https://mytarget.com", "cve_id": "CVE-XXXX-XXXX", "name": "Bad vuln", "severity": "CRITICAL"},
]
```

An integration of `mytool` with `secator` would look like:

{% code title="secator/tasks/mytool.py" %}

```python
from secator.decorators import task
from secator.runners import Command
from secator.output_types import Url, Tag, Vulnerability
from secator.serializers import RegexSerializer
from secator.tasks._categories import Vuln

OUTPUT_REGEX = r'\[INF\] JSON report will be saved to (?P<output_path>)'


@task()
class mytool(Command):
  cmd = '/home/osboxes/.local/bin/mytool'
  input_flag = '-u'
  json_flag = '-format json'
  output_types = [Url, Vulnerability]

  # Set the output file path
  @staticmethod
  def on_init(self):
    self.output_path = self.get_opt_value(OUTPUT_PATH)
    if not self.output_path:
      self.output_path = f'{self.reports_folder}/.outputs/{self.unique_name}.json'
      self.cmd += f' -o {self.output_path}'

  # When the command completes, load the JSON file and yield secator output types
  @staticmethod
  def on_cmd_done(self):
    with open(self.output_path, 'r') as f:
      results = f.read()
    for r in results:
      if r['type'] == 'url':
        yield Url(
          url=r['url'],
          status_code=r['status'],
        )
      elif r['type'] == 'vulnerability':
        yield Vulnerability(
          id=r['cve_id'],
          name=r['name'],
          matched_at=r['target'],
          severity=r['severity'].lower()
        )
  
```

{% endcode %}

Run it with `secator`:

{% tabs %}
{% tab title="CLI" %}

```bash
$ secator x mytool mytarget.com

                         __            
   ________  _________ _/ /_____  _____
  / ___/ _ \/ ___/ __ `/ __/ __ \/ ___/
 (__  /  __/ /__/ /_/ / /_/ /_/ / /    
/____/\___/\___/\__,_/\__/\____/_/     v0.6.0

                        freelabz.com

No Celery worker alive.
/home/osboxes/.local/bin/mytool -u mytarget.com -format json -o <OUTPUT_FILE_PATH>
🔗 https://mytarget.com [200]
🚨 [Bad vuln 🡕] [critical] https://mytarget.com
```

{% endtab %}
{% endtabs %}

***

## Using a regular expression to get the file path

**Steps:**

* Use the `RegexSerializer` to read and set the output file path.
* Use the `on_cmd_done` hook to read the file and yield `secator` output types.

#### **Example:**

Assume `mytool` outputs to a JSON file like:

```bash
mytool -u mytarget.com -o json
[INF] JSON report will be saved to /path/to/custom/path.json
...
```

and the corresponding JSON would look like:

```json
[
  { "type": "url", "url": "http://mytarget.com", "status": 200 },
  { "type": "vulnerability", "target": "https://mytarget.com", "cve_id": "CVE-XXXX-XXXX", "description": "Bad vuln" },
]
```

First we need to find a named regular expression that will match the filename.

Here is the one we came up with:

{% code overflow="wrap" %}

```python
OUTPUT_REGEX = r'\[INF\] JSON report will be saved to (?P<output_path>)'
```

{% endcode %}

An integration of `mytool` with `secator` would look like:

{% code title="secator/tasks/mytool.py" %}

```python
from secator.decorators import task
from secator.runners import Command
from secator.output_types import Url, Tag, Vulnerability
from secator.serializers import RegexSerializer
from secator.tasks._categories import Vuln

OUTPUT_REGEX = r'\[INF\] JSON report will be saved to (?P<output_path>)'


@task()
class mytool(Command):
  cmd = '/home/osboxes/.local/bin/mytool'
  input_flag = '-u'
  json_flag = '-o json'
  output_types = [Url, Vulnerability]

  # Override the default item loader (JSONSerializer) with the RegexSerializer
  item_loaders = [
    RegexSerializer(
      OUTPUT_REGEX,
      fields=['output_path']
    )
  ]

  # React to items loaded by the RegexSerializer, and set the output path
  @staticmethod
  def on_regex_loaded(self, item):
    self.output_path = item['output_path']
    return

  # When the command completes, load the JSON file and yield secator output types
  @staticmethod
  def on_cmd_done(self):
    with open(self.output_path, 'r') as f:
      results = f.read()
    for r in results:
      if r['type'] == 'url':
        yield Url(
          url=r['url'],
          status_code=r['status'],
        )
      elif r['type'] == 'vulnerability':
        yield Vulnerability(
          id=r['cve_id'],
          name=r['name'],
          matched_at=r['target'],
          severity=r['severity'].lower()
        )

```

{% endcode %}

Run it with `secator`:

{% tabs %}
{% tab title="CLI" %}

```bash
$ secator x mytool mytarget.com

                         __            
   ________  _________ _/ /_____  _____
  / ___/ _ \/ ___/ __ `/ __/ __ \/ ___/
 (__  /  __/ /__/ /_/ / /_/ /_/ / /    
/____/\___/\___/\__,_/\__/\____/_/     v0.6.0

                        freelabz.com

No Celery worker alive.
/home/osboxes/.local/bin/mytool -u mytarget.com -o json
...
[INF] JSON report will be saved to /path/to/custom/path.json
🔗 https://mytarget.com [200]
🚨 [Bad vuln 🡕] [critical] https://mytarget.com
```

{% endtab %}

{% tab title="Python" %}

```python
from secator.tasks import mytool
task = mytool('mytarget.com')
for item in task:
    print(item)  # this will output Url, Vulnerability, or Tag items.

```

{% endtab %}
{% endtabs %}

***


# Example: integrating ls

... or how to integrate a command without JSON output.

This section will present a relatively simple (but complete) use case of integrating a **real-world** command into `secator`.

We picked the `ls` command because:

* It is relatively simple.
* It has no JSON output (we will need to fabricate it).
* It has no direct secator output type mapping.

***

## Writing the task file

Start by creating a file named `ls.py`:

{% code title="ls.py" %}

```python
from secator.runners import Command
from secator.decorators import task

@task()
class ls(Command):
    cmd = 'ls'
```

{% endcode %}

Move this file over to `~/.secator/templates/` (if you modified the default `dirs.templates`, move it to the corresponding location instead).

You can test the initial implementation like so:

<pre class="language-bash"><code class="lang-bash"><strong>$ secator x ls .
</strong>ls .
ls.py
__pycache__
tasks
🗄 Saved JSON report to ~/.secator/reports/default/tasks/94/report.json
🗄 Saved CSV reports to ~/.secator/reports/default/tasks/94/report_target.csv
</code></pre>

Okay, this works !

{% hint style="warning" %} <mark style="color:red;">**YES**</mark><mark style="color:red;">, but we don't have enough details in the results !</mark>
{% endhint %}

***

## Getting detailed output

To add more details to the results, we should add more beef to the ls command, let's try with `ls -al` instead as the default `cmd` instead:

{% code title="\~/.secator/templates/ls.py" %}

```python
from secator.runners import Command
from secator.decorators import task

@task()
class ls(Command):
    cmd = 'ls -al'
```

{% endcode %}

and the output:

```bash
$ secator x ls .
ls -al .
total 16
drwxr-xr-x 3 osboxes osboxes 4096 May  2 04:50 .
drwxr-xr-x 4 osboxes osboxes 4096 May  2 04:49 ..
-rw-r--r-- 1 osboxes osboxes  119 May  2 04:51 ls.py
drwxr-xr-x 2 osboxes osboxes 4096 May  2 04:51 __pycache__
🗄 Saved JSON report to ~/.secator/reports/default/tasks/1/report.json
🗄 Saved CSV reports to ~/.secator/reports/default/tasks/1/report_target.csv
```

Okay, this gives more information already !

{% hint style="warning" %} <mark style="color:red;">**YES**</mark><mark style="color:red;">, but we don't have any structured output ! I can't use -json and pipe the results to my super awesome CLI tool ...</mark>
{% endhint %}

***

## Adding JSON output

For this step we need to parse the `ls` command line text output by writing the `item_loader` method.

The `item_loader` method takes a **line** as input and yield the **desired structured output** (dict).

Here is how to implement it for the `ls` command:

{% code title="\~/.secator/templates/ls.py" %}

```python
from secator.runners import Command
from secator.decorators import task


@task()
class ls(Command):
    cmd = 'ls -al'

    @staticmethod
    def item_loader(self, line):
        fields = ['permissions', 'link_count', 'owner', 'group', 'size', 'month', 'day', 'hour', 'path']
        result = [c for c in line.split(' ') if c]
        if len(result) != len(fields):
    	    return None
        data = {}
        for ix, value in enumerate(result):
    	    data[fields[ix]] = value
        yield data

```

{% endcode %}

and the output:

```bash
$ secator x ls . --json
ls -al .
total 16
❌ Failed to load item as output type:
  {'permissions': 'drwxr-xr-x', 'link_count': '3', 'owner': 'osboxes', 'group': 'osboxes', 'size': '4096', 'month': 'May', 'day': '2', 'hour': '04:50', 'path': '.', '_type': 'unknown', '_context': {'workspace_name': 'default'}, '_source': 'ls', '_uuid': 'fa90033a-56a4-4df1-b280-bb6f7cfdcd37'}
❌ Failed to load item as output type:
  {'permissions': 'drwxr-xr-x', 'link_count': '4', 'owner': 'osboxes', 'group': 'osboxes', 'size': '4096', 'month': 'May', 'day': '2', 'hour': '04:49', 'path': '..', '_type': 'unknown', '_context': {'workspace_name': 'default'}, '_source': 'ls', '_uuid': 'a76c01e4-8e07-4bdd-a2cf-66e96f1cd112'}
❌ Failed to load item as output type:
  {'permissions': '-rw-r--r--', 'link_count': '1', 'owner': 'osboxes', 'group': 'osboxes', 'size': '508', 'month': 'May', 'day': '2', 'hour': '04:55', 'path': 'ls.py', '_type': 'unknown', '_context': {'workspace_name': 'default'}, '_source': 'ls', '_uuid': '7189352a-fd06-493e-9bf8-de8e4a083782'}
❌ Failed to load item as output type:
  {'permissions': 'drwxr-xr-x', 'link_count': '2', 'owner': 'osboxes', 'group': 'osboxes', 'size': '4096', 'month': 'May', 'day': '2', 'hour': '04:55', 'path': '__pycache__', '_type': 'unknown', '_context': {'workspace_name': 'default'}, '_source': 'ls', '_uuid': '2c1d78b6-0c4d-4fa8-90c8-d08f8eda4922'}
🗄 Saved JSON report to ~/.secator/reports/default/tasks/2/report.json
🗄 Saved CSV reports to ~/.secator/reports/default/tasks/2/report_target.csv
❗Found 0 results.
```

The JSON objects are properly output, but they fail to convert with an existing [Output types](/in-depth/concepts/output-types).

To get the original JSON output, run with `--orig` flag:

```bash
$ secator x ls . --json --orig
ls -al .
total 16
{"permissions": "drwxr-xr-x", "link_count": "3", "owner": "osboxes", "group": "osboxes", "size": "4096", "month": "May", "day": "2", "hour": "04:50", "filename": ".", "_context": {"workspace_name": "default"}, "_source": "ls", "_uuid": "4aafe790-286a-4d59-9376-e6867d9bed6d", "_type": {}}
{"permissions": "drwxr-xr-x", "link_count": "4", "owner": "osboxes", "group": "osboxes", "size": "4096", "month": "May", "day": "2", "hour": "04:49", "filename": "..", "_context": {"workspace_name": "default"}, "_source": "ls", "_uuid": "733a0336-eef4-4601-a420-8de68315c3e3", "_type": {}}
{"permissions": "-rw-r--r--", "link_count": "1", "owner": "osboxes", "group": "osboxes", "size": "508", "month": "May", "day": "2", "hour": "04:55", "filename": "ls.py", "_context": {"workspace_name": "default"}, "_source": "ls", "_uuid": "f06836e1-844b-4e5d-9978-9fbbdba50d9c", "_type": {}}
{"permissions": "drwxr-xr-x", "link_count": "2", "owner": "osboxes", "group": "osboxes", "size": "4096", "month": "May", "day": "2", "hour": "04:56", "filename": "__pycache__", "_context": {"workspace_name": "default"}, "_source": "ls", "_uuid": "9eb6b94a-4765-4764-9d48-89f2fa72e327", "_type": {}}
🗄 Saved JSON report to ~/.secator/reports/default/tasks/3/report.json
🗄 Saved CSV reports to ~/.secator/reports/default/tasks/3/report_target.csv
```

Ok, in a few lines of code we successfully managed to turn the `ls` output into structured JSON lines.

{% hint style="warning" %} <mark style="color:red;">**YES**</mark><mark style="color:red;">, but we don't have anything in the JSON reports !</mark>
{% endhint %}

***

## Mapping output types

To get some useful results that secator reports understand, we need to map this arbitrary JSON output to one of the existing output type that `secator` provides. For instance, the `Vulnerability` output type !

For instance, we could consider as a vulnerability any path that is executable by the public. That's the final `w` in the permission string.

Let's change the implementation to output objects of type `Vulnerability`:

{% code title="\~/.secator/templates/ls.py" %}

```python
from secator.runners import Command
from secator.decorators import task
from secator.output_types import Vulnerability


@task()
class ls(Command):
    cmd = 'ls -al'
    output_types = [Vulnerability]

    @staticmethod
    def item_loader(self, line):
        fields = ['permissions', 'link_count', 'owner', 'group', 'size', 'month', 'day', 'hour', 'path']
        result = [c for c in line.split(' ') if c]
        if len(result) != len(fields):
            return None
        data = {}
        for ix, value in enumerate(result):
            data[fields[ix]] = value

        # Output vulnerabilities
        permissions = data['permissions']
        path = data['path']
        full_path = f'{self.input}/{path}'
        if permissions[-2] == 'w':  # found a vulnerability !
            yield Vulnerability(
                name='World-writeable path',
                severity='high',
                confidence='high',
                provider='ls',
                matched_at=full_path,
                extra_data={k: v for k, v in data.items() if k != 'path'}
            )

```

{% endcode %}

Let's make the `ls.py` file world-writeable with `chmod a+w ls.py` to create a vulnerability, and re-run our command:

```bash
$ secator x ls .
ls -al .
total 16
drwxr-xr-x 3 osboxes osboxes 4096 May  2 06:14 .
drwxr-xr-x 4 osboxes osboxes 4096 May  2 04:49 ..
🚨 [World-writeable path 🡕] [high] ./ls.py [permissions:-rw-rw-rw-, link_count:1, owner:osboxes, group:osboxes, size:1015, month:May, day:2, hour:06:14]
drwxr-xr-x 2 osboxes osboxes 4096 May  2 06:14 __pycache__
🗄 Saved JSON report to /home/osboxes/.secator/reports/default/tasks/140/report.json
🗄 Saved CSV reports to 
   • /home/osboxes/.secator/reports/default/tasks/140/report_target.csv
   • /home/osboxes/.secator/reports/default/tasks/140/report_vulnerability.csv
✔ Found 1 vulnerability.
```

{% hint style="success" %}
We have successfully integrated the command `ls` with `secator` !
{% endhint %}

***


# Example: cat hunters

... or how to integrate groups of tasks with similar options.

This section will present a more complex use case where we have three commands: `bigdog`, `catkiller` and `eagle` which purpose is to find cats.

We will start by integrating `bigdog` to `secator` before realizing that most options can be mutualized between the three tools, and a common output type `Cat` can be created for all three.

***

## Bigdog

Let's suppose we have a **fictional** utility called `bigdog` which purpose is to hunt cats on the internet. We want to add `bigdog` to `secator`.

Here are some of `bigdog`'s options:

{% tabs %}
{% tab title="-site" %}
`bigdog` can be run on a single site using `-site`:

```sh
$ bigdog -site loadsofcats.com
   / \__
  (    @\___   =============
  /         O  BIGDOG v1.0.0
 /   (_____/   =============
/_____/
garfield [boss, 14]
tony [admin, 18]
```

{% endtab %}

{% tab title="-list" %}
`bigdog` can be run on a list of sites using `-list`:

```sh
$ bigdog -list sites.txt -json
   / \__
  (    @\___   =============
  /         O  BIGDOG v1.0.0
 /   (_____/   =============
/_____/
garfield [boss, 14]
romuald [minion, 5]
tony [admin, 18]
```

{% endtab %}

{% tab title="-json" %}
`bigdog` can output JSON lines using `-json`:

```sh
$ bigdog -site loadsofcats.com -json
   / \__
  (    @\___   =============
  /         O  BIGDOG v1.0.0
 /   (_____/   =============
/_____/
{"name": "garfield", "age": 14, "host": "loadsofcat.com", "position": "boss"}
{"name": "tony", "age": 18, "host": "loadsofcats.com", "position": "admin"}
```

{% endtab %}
{% endtabs %}

A basic definition of `bigdog` using basic `secator` concepts will be:

{% code title="secator/tasks/bigdog.py" lineNumbers="true" %}

```py
from secator.runners import Command
from secator.decorators import task


@task
class bigdog(Command):
    cmd = 'bigdog'
    json_flag = '-json'
    input_flag = '-site'
    file_flag = '-list'
```

{% endcode %}

You can now run `bigdog` from the CLI or the library:

{% tabs %}
{% tab title="CLI" %}

```bash
secator x bigdog --help
secator x bigdog loadsofcats.com
```

{% endtab %}

{% tab title="Python" %}

```python
from secator.tasks import bigdog

# Get all results as a list, blocks until command has finished running
bigdog('loadsofcats.com').run()
[
    {"name": "garfield", "age": 14, "host": "loadofcats.com", "position": "boss"},
    {"name": "tony", "age": 18, "host": "loadsofcats.com", "position": "admin"}
]

# Get result items in real-time as they arrive to stdout
for cat in bigdog('loadsofcats.com'):
    print(cat['name'] + '(' + cat['age'] + ')')

# Will print
garfield (14)
tony (18)
```

{% endtab %}
{% endtabs %}

Okay, this is a good start.

Now what if the `bigdog` command has some more options that you would like to integrate ?

* `-timeout` allows to specify a request timeout.
* `-rate` allows to specify the max requests per minute.

You can add the `opts` parameter to your `Command` object to define the cmd options:

<pre class="language-py" data-line-numbers><code class="lang-py"><strong>from secator.runners import Command
</strong>from secator.decorators import task


@task
class bigdog(Command):
    cmd = 'bigdog'
    json_flag = '-json'
    input_flag = '-site'
    file_flag = '-list'
    opt_prefix = '-'
    opts = {
        'timeout': {'type': int, 'help': 'Timeout (in seconds)'},
        'rate': {'type': int, 'help': 'Max requests per minute'}
    }
</code></pre>

You can now use `bigdog` with this set of options:

{% tabs %}
{% tab title="CLI" %}

```bash
secator x bigdog --help
secator x bigdog loadsofcats.com -json
secator x bigdog loadsofcats.com -timeout 1 -rate 100 -o table,csv,txt,gdrive
```

{% endtab %}

{% tab title="Python" %}

```python
from secator.tasks import bigdog
bigdog('loadsofcats.com', rate=100, timeout=3).run()  # adding rate and timeout options
```

{% endtab %}
{% endtabs %}

***

## Cat hunters category

One advantage of having class-based definitions is that we can group similar tools together in categories.

Let's assume we have 2 other tools that can hunt cats: `catkiller` and `eagle`...

... but each of those tools might be written by a different person, and so the interface and output is different for each of them:

{% tabs %}
{% tab title="catkiller" %}

<pre class="language-bash"><code class="lang-bash"><strong>$ catkiller --host loadsofcats.com --max-wait 1000 --max-rate 10 --json
</strong>Starting catkiller session ...
{"_info": {"name": "tony", "years": 18}, "site": "loadsofcats.com", "job": "admin"}
{"_info": {"name": "garfield", "years": 14}, "site": "loadsofcats.com", "job": "boss"}

# or to pass multiple hosts, it needs to be called like:
$ cat hosts.txt | catkiller --max-wait 1000 --max-rate 10 --json
</code></pre>

**Inputs:**

* `--host` is equivalent to `bigdog`'s `-site`.
* `--max-wait` is equivalent to `bigdog`'s `-timeout`, but in milliseconds instead of seconds.
* `--max-rate` is equivalent to `bigdog`'s `-rate`.
* `--json` is equivalent to `bigdog's` `-json` option, but uses a different option character "`--`".
* `cat hosts.txt | catkiller` is the equivalent to`bigdog`'s `-list`.

**Output:**

* `_info` has the data for `name` and `age`, but `age` is now `years`.
* `site` is the equivalent of `bigdog`'s `host`.
* `job` is the equivalent of `bigdog`'s `position`.
  {% endtab %}

{% tab title="eagle" %}

<pre class="language-bash"><code class="lang-bash"><strong>$ eagle -u loadsofcats.com -timeexpires 1 -jsonl
</strong>                  _      
                 | |     
  ___  __ _  __ _| | ___ 
 / _ \/ _` |/ _` | |/ _ \
|  __/ (_| | (_| | |  __/  v2.2.0
 \___|\__,_|\__, |_|\___|
             __/ |       
            |___/       
{"alias": "tony", "occupation": "admin", "human_age": 105}

# or to pass multiple hosts, it needs to be called like:
$ eagle -l hosts.txt -timeexpires 1 -jsonl
                  _      
                 | |     
  ___  __ _  __ _| | ___ 
 / _ \/ _` |/ _` | |/ _ \
|  __/ (_| | (_| | |  __/  v2.2.0
 \___|\__,_|\__, |_|\___|
             __/ |       
            |___/    
{"alias": "tony", "occupation": "admin", "human_age": 105, "host": "loadsofcats.com"}
</code></pre>

**Inputs:**

* `-u` is equivalent to `bigdog`'s `-site`.
* `-l` is equivalent to `bigdog`'s `-list`.
* `-timeexpires` is equivalent to `bigdog`'s `-timeout`.
* `eagle` **does not support** setting the maximum requests per seconds (`bigdog`'s `-rate`).
* `-jsonl` is the flag to output JSON lines, instead of `bigdog`'s `-json`.

**Output:**

* `alias` is the equivalent of `bigdog`'s `name`.
* `occupation` is the equivalent of `bigdog`'s `job`.
* `human_age` is the human age conversion of the cat age.
  {% endtab %}
  {% endtabs %}

### Cat output type

We first define a base `Cat` dataclass to define the common output schema and a `CatHunter` category as an input interface.

We take `bigdog`'s output schema as reference to create the `Cat` output type:

{% code title="secator/output\_types/cat.py" %}

```py
from secator.definitions import OPT_NOT_SUPPORTED
from secator.output_types import OutputType
from secator.decorators import task
from dataclasses import dataclass, field


@dataclass
class Cat(OutputType):
    name: str
    age: int
    alive: bool = False
    _source: str = field(default='', repr=True)
    _type: str = field(default='cat', repr=True)
    _uuid: str = field(default='', repr=True, compare=False)

    _table_fields = [name, age]
    _sort_by = (name, age)

    def __str__(self) -> str:
        return self.ip

```

{% endcode %}

### CatHunter category

We take `bigdog`'s options names as reference and add the ones that can be mutualized to the `CatHunter` category:

{% code title="secator/tasks/\_categories.py" %}

```python
from secator.output_types import Cat
# ...

class CatHunter(Command):
    meta_opts = {
        'timeout': {'type': int, 'default': 1, 'help': 'Timeout (in seconds)'},
        'rate': {'type': int, 'default': 1000, 'help': 'Max requests per minute'},
    }
    output_types = [Cat]
```

{% endcode %}

### Tools implementation

Finally we inherit all commands implementation from `CatHunter` and write the option mapping for the remaining cat-hunter commands:

{% tabs %}
{% tab title="bigdog" %}
{% code title="secator/tasks/bigdog.py" %}

```python
from secator.categories import CatHunter
from secator.decorators import task


@task()
class bigdog(CatHunter):
    cmd = 'bigdog'
    json_flag = '-json'
    input_flag = '-site'
    file_flag = '-list'
    opt_prefix = '-'
```

{% endcode %}
{% endtab %}

{% tab title="catkiller" %}
{% code title="secator/tasks/catkiller.py" %}

```python
from secator.categories import CatHunter
from secator.decorators import task
from secator.output_types import Cat


@task()
class catkiller(CatHunter):
    cmd = 'catkiller'
    json_flag = '--json'
    input_flag = '--host'

    # stdin-like input using 'cat <FILE> | <COMMAND>'
    file_flag = None

    # catkiller options start with "--" unlike the other tools
    opt_prefix = '--' 

    # Map `catkiller` options to CatHunter.meta_opts
    opt_key_map = {
        'rate': 'max-rate'
        'timeout': 'max-wait'
    }
    opt_value_map = {
        'timeout': lambda x: x / 1000 # converting milliseconds to seconds
    }

    # Map `catkiller` output schema to Cat schema
    output_map = {
	Cat: {
	    'name': lambda x: x['_info']['name'], # note: you can use any function, we use
	    'age': lambda x: x['_info']['age'],   #       lambdas for readability here
	    'host': 'site',   # 1:1 mapping
	    'job': 'job' # 1:1 mapping
	}
    }
```

{% endcode %}
{% endtab %}

{% tab title="eagle" %}

<pre class="language-python" data-title="secator/tasks/eagle.py"><code class="lang-python">from secator.categories import CatHunter
from secator.decorators import task
from secator.definitions import OPT_NOT_SUPPORTED
from secator.output_types import Cat


<strong>@task()
</strong>class eagle(CatHunter):
    cmd = 'eagle'
    json_flag = '-jsonl'
    input_flag = '-u'
    file_flag = '-l'

    # Map `eagle` input options to CatHunter.meta_opts
    opt_key_map = {
        'rate': 'timeexpires',
        'timeout': OPT_NOT_SUPPORTED # explicitely state that this option not supported by the target tool
    }

    # Map `eagle` output schema to Cat schema:
    output_map = {
	Cat: {
	    'name': 'alias',
	    'age': lambda x: human_to_cat_age(x['human_age']),
	    'job': 'occupation',
	}
    }

    # Add 'host' key dynamically after the item has been converted to the output schema,
    # since `eagle` doesn't return the host systematically.
    @staticmethod
    def on_item(self, item):
        item['host'] = item.get('host') or self.input
        return item


def human_to_cat_age(human_age):
    cat_age = 0
    if human_age &#x3C;= 22:
        cat_age = human_age // 11
    else:
        cat_age = (human_age - 22) // 5 + 2
    return cat_age
</code></pre>

{% endtab %}
{% endtabs %}

Using these definitions, we can now use all the cat-hunter commands with a common interface (input options & output schema):

{% tabs %}
{% tab title="CLI" %}

```bash
secator x bigdog loadsofcats.com -rate 1000 -timeout 1 -json
secator x eagle loadsofcats.com -rate 1000 -timeout 1 -json
secator x catkiller loadsofcats.com -rate 1000 -timeout 1 -json
```

{% endtab %}

{% tab title="Python" %}

```python
>>> from secator.tasks import bigdog, catkiller, eagle
>>> meta_opts = {'timeout': 1, 'rate': 1000, 'json': True}
>>> bigdog('loadsofcats.com', **meta_opts).run()
[
    Cat(name="garfield", age=14, host="loadsofcats.com", position="boss", _source="bigdog"),
    Cat(name="tony", age=18, host="loadsofcats.com", position="admin", _source="bigdog")
]
>>> catkiller('catrunner.com', **meta_opts).run()
[
    Cat(name=fred, age=12, host="catrunner.com", position="minion", _source="catkiller"},
    Cat(name=mark, age=20, host="catrunner.com", position="minion", _source="catkiller"}
]
>>> eagle('allthecats.com', **meta_opts).run()
[
    Cat(name="marcus", age=4, host="allthecats.com", position="minion", _source="eagle"},
    Cat(name="rafik", age=7, host="allthecats.com", position="minion", _source="eagle"}
]
```

{% endtab %}
{% endtabs %}

***


# Integrate custom Python code \[WIP]

How to create custom tasks using pure Python code without external commands.

The `PythonRunner` class allows you to create custom Secator tasks using pure Python code, without needing to integrate external command-line tools. This is useful when you want to:

* Process data using Python libraries
* Implement custom logic that doesn't require external commands
* Create lightweight tasks that perform transformations or analysis
* Build tasks that interact with APIs or databases

***

## Overview

To create a custom Python task, you need to:

1. Create a Python file in `~/.secator/templates/` (for user tasks) or `secator/tasks/` (for development)
2. Inherit from the `PythonRunner` class
3. Decorate your class with the `@task()` decorator
4. Define `input_types` and `output_types`
5. Implement the `yielder()` method to yield results

***

## Basic structure

Here's the minimal structure of a Python task:

{% code title="\~/.secator/templates/mytask.py" %}

```python
from secator.decorators import task
from secator.definitions import HOST
from secator.output_types import Tag, Url
from secator.runners import PythonRunner


@task()
class mytask(PythonRunner):
    """Description of what this task does."""
    input_types = [HOST]
    output_types = [Tag, Url]

    def yielder(self):
        for target in self.inputs:
            yield Url(url=f"http://{target}")
            yield Tag(name="scanned", match=target)
```

{% endcode %}

**Important notes:**

* The class name must match the filename (without `.py`)
* The `@task()` decorator is required
* You must inherit from `PythonRunner`
* The `yielder()` method is where your task logic goes
* Use `yield` to return results as output types

***

## Available input types

You can specify which input types your task accepts using `input_types`. Available input types include:

* `HOST` - Hostnames or domains
* `URL` - URLs
* `IP` - IP addresses
* `CIDR_RANGE` - CIDR ranges
* `EMAIL` - Email addresses
* `PATH` - File paths
* `STRING` - Generic strings
* `None` - Accept any input type

Example:

```python
from secator.definitions import URL, IP, HOST

@task()
class mytask(PythonRunner):
    # Accept multiple input types
    input_types = [URL, IP, HOST]
    # ...
```

***

## Available output types

Your task can yield various output types. Common ones include:

* `Info` - Informational messages
* `Url` - URLs
* `Ip` - IP addresses
* `Port` - Network ports
* `Subdomain` - Subdomains
* `Vulnerability` - Security vulnerabilities
* `Tag` - Tags/metadata
* `Domain` - Domains
* `Record` - DNS records
* `Certificate` - SSL certificates
* `UserAccount` - User accounts
* `Warning` - Warning messages
* `Error` - Error messages

Example:

```python
from secator.output_types import Url, Vulnerability, Tag, Info

@task()
class mytask(PythonRunner):
    output_types = [Url, Vulnerability, Tag, Info]
    # ...
```

***

## Example: Simple URL processor

This example processes URLs and extracts information:

{% code title="\~/.secator/templates/urlprocessor.py" %}

```python
from urllib.parse import urlparse

from secator.decorators import task
from secator.definitions import URL
from secator.output_types import Tag, Url
from secator.runners import PythonRunner


@task()
class urlprocessor(PythonRunner):
    """Extract components from URLs."""
    input_types = [URL]
    output_types = [Tag, Url]
    tags = ['url', 'parsing']

    def yielder(self):
        for url in self.inputs:
            parsed = urlparse(url)
            
            # Yield the original URL
            yield Url(url=url)
            
            # Yield tags with extracted information
            yield Tag(
                name='url_scheme',
                value=parsed.scheme,
                match=url,
                category='info'
            )
            yield Tag(
                name='url_domain',
                value=parsed.netloc,
                match=url,
                category='info'
            )
            yield Tag(
                name='url_path',
                value=parsed.path,
                match=url,
                category='info'
            )
```

{% endcode %}

Usage:

```bash
secator x urlprocessor https://example.com/path?query=1
```

***

## Example: Vulnerability scanner

This example demonstrates yielding vulnerabilities:

{% code title="\~/.secator/templates/customscanner.py" %}

```python
import requests

from secator.decorators import task
from secator.definitions import URL
from secator.output_types import Vulnerability, Info, Warning
from secator.runners import PythonRunner


@task()
class customscanner(PythonRunner):
    """Custom vulnerability scanner."""
    input_types = [URL]
    output_types = [Vulnerability, Info, Warning]
    tags = ['vuln', 'web']

    def yielder(self):
        yield Info(message="Starting custom scan")
        
        for url in self.inputs:
            try:
                response = requests.get(url, timeout=5)
                
                # Check for security headers
                if 'X-Frame-Options' not in response.headers:
                    yield Vulnerability(
                        name="Missing X-Frame-Options header",
                        severity="medium",
                        confidence="high",
                        matched_at=url,
                        provider="customscanner"
                    )
                
                # Check for exposed server information
                if 'Server' in response.headers:
                    server = response.headers['Server']
                    yield Warning(
                        message=f"Server header exposed: {server}",
                        matched_at=url
                    )
                    
            except requests.RequestException as e:
                yield Warning(
                    message=f"Failed to scan {url}: {str(e)}",
                    matched_at=url
                )
        
        yield Info(message="Scan complete")
```

{% endcode %}

***

## Example: Task with custom options

You can define custom options that users can pass to your task:

{% code title="\~/.secator/templates/tagger.py" %}

```python
from secator.decorators import task
from secator.definitions import HOST
from secator.output_types import Tag
from secator.runners import PythonRunner


@task()
class tagger(PythonRunner):
    """Tag hosts with custom metadata."""
    input_types = [HOST]
    output_types = [Tag]
    tags = ['tagging']
    
    opts = {
        'tag_name': {
            'type': str,
            'default': 'custom_tag',
            'help': 'Name of the tag to apply'
        },
        'tag_value': {
            'type': str,
            'default': 'scanned',
            'help': 'Value for the tag'
        },
        'category': {
            'type': str,
            'default': 'info',
            'help': 'Tag category'
        }
    }

    def yielder(self):
        tag_name = self.run_opts.get('tag_name', 'custom_tag')
        tag_value = self.run_opts.get('tag_value', 'scanned')
        category = self.run_opts.get('category', 'info')
        
        for host in self.inputs:
            yield Tag(
                name=tag_name,
                value=tag_value,
                match=host,
                category=category
            )
```

{% endcode %}

Usage:

```bash
secator x tagger example.com --tag-name "environment" --tag-value "production"
```

***

## Example: Task without inputs

Some tasks don't require inputs. Use `default_inputs` to make inputs optional:

{% code title="\~/.secator/templates/netdetect.py" %}

```python
import ifaddr
import ipaddress

from secator.decorators import task
from secator.output_types import Tag, Ip
from secator.runners import PythonRunner


@task()
class netdetect(PythonRunner):
    """Detect local network CIDR ranges."""
    output_types = [Tag, Ip]
    tags = ['network', 'recon']
    default_inputs = ''  # No inputs required
    input_flag = None

    def yielder(self):
        adapters = ifaddr.get_adapters()
        for adapter in adapters:
            if adapter.name == 'lo' or adapter.name.lower().startswith('loopback'):
                continue
                
            yield Tag(
                name='net_interface',
                match='localhost',
                value=adapter.nice_name,
                category='info',
            )
            
            for ip in adapter.ips:
                if ip.is_IPv4:
                    try:
                        network = ipaddress.IPv4Network(
                            f"{ip.ip}/{ip.network_prefix}",
                            strict=False
                        )
                        yield Ip(
                            ip=ip.ip,
                            host='localhost',
                            alive=True,
                        )
                        yield Tag(
                            name='net_cidr',
                            match='localhost',
                            value=str(network),
                            category='info',
                        )
                    except ValueError:
                        continue
```

{% endcode %}

Usage:

```bash
secator x netdetect
```

***

## Accessing runner properties

In your `yielder()` method, you have access to several useful properties:

* `self.inputs` - List of input values
* `self.run_opts` - Dictionary of run options (including custom `opts`)
* `self.name` - Task name
* `self.config` - Task configuration
* `self.context` - Runner context

Example:

```python
def yielder(self):
    yield Info(message=f"Task name: {self.name}")
    yield Info(message=f"Processing {len(self.inputs)} inputs")
    
    # Access custom options
    timeout = self.run_opts.get('timeout', 10)
    
    for input in self.inputs:
        # Process each input
        pass
```

***

## Advanced: Multiple output types

You can yield different output types based on your logic:

{% code title="\~/.secator/templates/multiplexer.py" %}

```python
from secator.decorators import task
from secator.definitions import HOST
from secator.output_types import Url, Vulnerability, Tag, Info
from secator.runners import PythonRunner


@task()
class multiplexer(PythonRunner):
    """Process hosts and yield different output types."""
    input_types = [HOST]
    output_types = [Url, Vulnerability, Tag, Info]

    def yielder(self):
        yield Info(message="Starting multiplex scan")
        
        for host in self.inputs:
            # Always yield a URL
            yield Url(url=f"https://{host}")
            
            # Conditionally yield vulnerabilities
            if "test" in host.lower():
                yield Vulnerability(
                    name="Test environment detected",
                    severity="low",
                    confidence="high",
                    matched_at=host,
                    provider="multiplexer"
                )
            
            # Yield tags
            yield Tag(
                name="scanned_host",
                value=host,
                match=host,
                category="info"
            )
        
        yield Info(message="Scan complete")
```

{% endcode %}

***

## Task discovery

Secator automatically discovers tasks from:

* **User tasks**: `~/.secator/templates/*.py`
* **Development tasks**: `secator/tasks/*.py` (when developing Secator itself)

The task class name must match the filename (case-sensitive). For example:

* File: `~/.secator/templates/mytask.py` → Class: `mytask`
* File: `secator/tasks/urlparser.py` → Class: `urlparser`

After creating your task file, Secator will automatically discover it on the next run.

***

## Best practices

1. **Use descriptive class names**: Choose names that clearly indicate what the task does
2. **Add docstrings**: Document what your task does in the class docstring
3. **Set appropriate tags**: Use `tags` to categorize your task for better discoverability
4. **Handle errors gracefully**: Use `Warning` or `Error` output types for error conditions
5. **Yield progress information**: Use `Info` messages to provide feedback during long-running tasks
6. **Validate inputs**: Check input validity before processing
7. **Use appropriate output types**: Choose the most specific output type for your results

***

## Testing your task

You can test your task from the command line:

```bash
# Run the task
secator x mytask example.com

# Get help
secator x mytask --help

# Run with JSON output
secator x mytask example.com --json

# Run with custom options
secator x mytask example.com --option1 value1
```

Or use it programmatically:

```python
from secator.tasks import mytask

# Run the task
results = mytask('example.com').run()

# Access results
for result in results:
    print(f"{result._type}: {result}")
```

***

## Common patterns

### Pattern: Processing with external libraries

```python
import some_library

@task()
class mytask(PythonRunner):
    def yielder(self):
        for input in self.inputs:
            result = some_library.process(input)
            yield Tag(name="result", value=result, match=input)
```

### Pattern: API integration

```python
import requests

@task()
class apitask(PythonRunner):
    def yielder(self):
        for input in self.inputs:
            response = requests.get(f"https://api.example.com/{input}")
            data = response.json()
            yield Tag(name="api_data", value=str(data), match=input)
```

### Pattern: Data transformation

```python
@task()
class transform(PythonRunner):
    def yielder(self):
        for input in self.inputs:
            # Transform the input
            transformed = input.upper().replace("-", "_")
            yield Tag(name="transformed", value=transformed, match=input)
```

***

## Troubleshooting

### Task not discovered

* Ensure the filename matches the class name exactly
* Check that the file is in `~/.secator/templates/` or `secator/tasks/`
* Verify the `@task()` decorator is present
* Make sure the class inherits from `PythonRunner`

### Validation errors

* If you get "Input is empty" errors, set `default_inputs = ''` for tasks that don't need inputs
* For tasks that accept multiple inputs, ensure you're running with a worker (multiple inputs aren't supported in non-worker mode)

### Import errors

* Ensure all required Python packages are installed
* Check that imports are correct and available in your environment


# Advanced options

There are additional class options and functions you can specify in the task definition class, based on which runner is used.

***

## `Command` runner

* `shell` (`bool`, `default: False`): Run `subprocess.Popen` with `shell=True` (**dangerous !**).
* `cwd` (`str`, `default: None`): Command current working directory.
* `encoding` (`dict`, `default: utf-8`): Output encoding.
* `opt_prefix` (`str`, `default: -`): Change the prefix used to specify command options.
* `version_flag` (`str`, `default: None`): The version flag. Defaults to `{opt_prefix}version`.
* `ignore_return_code` (`bool`, `default: False`): Ignore the command return code (useful if your command has non-standard return codes).

***


# Writing workflows

... or how to integrate new workflows with secator.

New `secator` workflows should be easy to write to promote contributions from the community.

Eventually we aim for our workflow library to become a reference in cyber-security much like Nuclei templates have become a reference for vulnerability searching.

***

## Basic YAML definition

`secator` workflows are defined through YAML configs:

{% code title="secator/configs/workflows/url\_finder.yaml" %}

```yaml
type: workflow
name: url_finder
alias: ufind
description: URL finder and tagger
tags: [http]
input_types:
  - url
tasks:
  katana:
    description: Find URLs
    rate_limit: 100
    timeout: 1
  gf:
    description: Tag URLs
    pattern: xss
```

{% endcode %}

***

## Dynamic targets

You can specify dynamic targets for tasks from current run results, by using the `targets_` key in your template like:

```yaml
...
tasks:
  ...
  gf:
    description: Tag URLs found by the previous task
    targets_:  # use previously found URLs
    - type: url
      field: url
      condition: item.status_code == 200
```

{% hint style="info" %}
The dynamic format keys are:

* `type` is the output type, lower-case (see [Output types](/in-depth/concepts/output-types) for the whole list)
* `field` is the JSON field to use as target
* `condition` is the filtering condition.
  {% endhint %}

***

## Concurrent tasks

You can specify tasks that run in parallel using the `_group` key:

```yaml
...
tasks:
  ...
  katana:
  _group:
    gf:
      ...
    another_task:
      description: Runs concurrently with the `gf` task
```

In this configuration, the `katana`task will begin the run, followed by a grouped execution of `gf` and `another_task`.

{% hint style="info" %}
Concurrent tasks require`secator` to be setup in worker mode (see [Distributed runs with Celery](/in-depth/distributed-runs-with-celery)).
{% endhint %}

***

## Result filtering

You can customize which results you want to keep for workflows and scans by adding the `results` key to the respective runner YAML configuration:

<pre class="language-yaml"><code class="lang-yaml"><strong>...
</strong><strong>results:
</strong>- type: ip
  condition: item.alive
- type: url
  condition: item.status_code == 200
</code></pre>

{% hint style="warning" %}
Filters apply to final run results sent to [Exporters](/in-depth/concepts/exporters), but do not apply to real-time results sent to [Drivers](/in-depth/concepts/drivers).
{% endhint %}

***


# Writing scans \[WIP]

... or how to integrate new scans with secator.


