> For the complete documentation index, see [llms.txt](https://docs.nullify.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.nullify.ai/capabilities/pentests/engagement-workflow.md).

# Engagement Workflow

You can launch a pentest from the Nullify dashboard or with the Nullify CLI.

Before you start, work through [Preparing for an Engagement](/capabilities/pentests/preparing-for-an-engagement.md). Engagements usually stall for one of three reasons: our source IP is not allowlisted, the test credentials do not authenticate, or there is no API specification.

## Dashboard

1. Go to **Assessments → Pentest → Applications**.
2. Select **Add Application** and give it a target — a hostname and port.
3. Attach one credential per role you want tested, then validate each one. A pentest cannot start until every attached credential authenticates.
4. Optionally run a **preflight** check, which confirms the application is ready to test and estimates cost and duration.
5. Start the run. Progress and findings stream into the dashboard and any connected ticketing or chat integrations.

![Launch a new pentest](/files/gpVnfiDE534MQCbenPTO)

Creating applications and credentials requires the **Admin** role. Starting and stopping scans requires **Editor**. If the **Start Pentest** button is not visible on your tenant, self-service pentesting has not been enabled yet — contact your Nullify representative.

Nullify discovers your API specification automatically where it can (see [Supported Targets](/capabilities/pentests/supported-targets.md)). There is no specification upload in the dashboard. To supply a specification file directly, use the CLI or send it to your Nullify contact.

## CLI

Use the CLI to run a pentest from your own environment, to wire one into a pipeline, or to test a service that is not reachable from the internet.

Install the CLI and authenticate:

```bash
curl -sSfL https://raw.githubusercontent.com/Nullify-Platform/cli/main/install.sh | sh -s -- --host api.<TENANT>.nullify.ai
nullify auth login --host api.<TENANT>.nullify.ai
```

In CI, set `NULLIFY_TOKEN` to a service-account token instead of logging in. See [API Configuration](/configuration/configuration-api.md) for how to generate one. Inside GitHub Actions you can pass the workflow's GitHub token with `--github-token` and the CLI exchanges it for a Nullify token — see [CLI Authentication](/cli/cli/authentication.md).

Run a pentest:

```bash
nullify pentest \
  --app-name      "Payments API" \
  --spec-path     "./openapi.json" \
  --target-host   "https://api.example.dev" \
  --github-owner  "my-org" \
  --github-repo   "payments-service" \
  --header        "Authorization: Bearer 1234" \
  --header        "X-Custom-Header: abcxyz"
```

`--spec-path` is required. Repeat `--header` once per header you want the pentester to send.

### Testing more than one role

Static headers test one identity. To find authorization flaws — IDOR, broken object-level authorization, privilege escalation — the pentester needs one credential set per role. Pass a config file with `--auth-config`:

```bash
nullify pentest \
  --app-name    "Payments API" \
  --spec-path   "./openapi.json" \
  --target-host "https://api.example.dev" \
  --auth-config "./pentest-auth.json"
```

`--auth-config` takes a JSON file — unlike `--spec-path`, this file is not parsed as YAML. The top-level `users` array holds one entry per role:

```json
{
  "users": [
    {
      "roleName": "admin",
      "roleDescription": "Administrator with full access",
      "authConfig": {
        "method": "bearer",
        "token": "<admin token>"
      }
    },
    {
      "roleName": "user",
      "roleDescription": "Standard user",
      "authConfig": {
        "method": "bearer",
        "token": "<user token>"
      }
    },
    {
      "roleName": "guest",
      "roleDescription": "Unauthenticated",
      "authConfig": {
        "method": "none"
      }
    }
  ]
}
```

The CLI does not expand `${VAR}` or template this file — whatever you write into `token` or `password` is read and sent as-is. Do not commit a populated auth-config file to source control: generate it at run time from your secret store (for example, write it to a temporary path in your CI job just before the `nullify pentest` step, then delete it) or keep it in a path covered by `.gitignore`.

### Running against a private network

Add `--local` to run the pentester inside your own network against hosts only you can reach. See [Running in Private Networks](/capabilities/pentests/private-networks.md).

## Continuous Integration

Run the CLI in your pipeline. In GitHub Actions, pass the workflow token and the CLI handles the rest:

```yaml
- name: Nullify pentest
  run: |
    nullify pentest \
      --github-token "${{ secrets.GITHUB_TOKEN }}" \
      --app-name     "Payments API" \
      --spec-path    "./openapi.json" \
      --target-host  "https://api.staging.example.dev"
```

Use `nullify ci gate` to fail a build on new findings and `nullify ci report` to publish results.

Pentests are slow and cost far more than a code scan, so run them on release branches or a schedule rather than on every commit.

## What to expect

|                  |                                   |
| ---------------- | --------------------------------- |
| Typical duration | One to three hours                |
| Maximum duration | Six hours                         |
| Concurrency      | One run per application at a time |
| Preflight checks | Five per application per day      |

Findings appear in the dashboard as they are confirmed. Each confirmed finding carries the request and response that proved it, plus a reproduction script. Download the PDF report from the run page once the engagement completes.
