# Introduction

Nullify is an autonomous AI that owns product security end-to-end. From finding vulnerabilities to shipping fixes, Nullify does the complete job of a product security engineer—continuously, across your entire attack surface.

## Onboarding

After connecting your repositories and cloud accounts, Nullify discovers your entire architecture—every service, API, dependency, and team relationship. It builds a comprehensive knowledge graph by mapping code ownership, understanding team structures, and learning what technologies power each service. This context informs every security decision.

## Continuous Assessments and Triage

Nullify operates 24/7 across your entire stack. In code repositories, it reviews every commit and pull request, examining first-party code for injection flaws, auth bypasses, and business logic bugs. It identifies CVEs in dependencies, reviews infrastructure-as-code for misconfigurations, and scans all branches for leaked secrets.

In cloud environments, Nullify continuously audits deployed infrastructure, tracking configuration drift, mapping resources back to source code, identifying exposed services, and validating compliance. For external attack surface, Nullify discovers internet-facing services, tests APIs with authenticated flows, chains vulnerabilities to demonstrate real attack paths, and validates exploitability.

Every finding gets investigated. Nullify analyzes code paths to determine reachability, attempts proof-of-concept exploits, and evaluates business context. Only validated, exploitable vulnerabilities proceed to remediation.

## Program Management

Nullify actively manages your security backlog. It performs threat research specific to your stack, tracking actively exploited CVEs and zero-days affecting your dependencies. Team capacity tracking monitors open pull requests and assigned tickets to understand workload. Nullify respects team boundaries and organizational ground rules. Critical vulnerabilities in customer-facing services get immediate attention while lower-risk issues wait for sprint planning.

Nullify opens pull requests with production-ready patches that match your codebase patterns. It analyzes your existing code to use your error handling, logging frameworks, and coding style. When CI fails, Nullify reads logs and pushes fixes. When developers leave review comments, it responds and updates code. If PRs stall, it sends reminders and escalates when necessary.

## Learning and Adaptation

Every interaction improves Nullify's effectiveness. Developer feedback refines fix patterns. Code review comments improve future patches. Team response times shape scheduling. This is evolution based on your organization's actual behavior.


# Install

Nullify is delivered as a dedicated enterprise tenant. Installation is coordinated with the Nullify team to ensure a secure identity boundary and the right connectors from day one.

1. Contact your Nullify representative (or email <hello@nullify.ai>) to provision a tenant.
2. Receive the tenant URL, admin credentials, and GitHub/Bitbucket app installation link.
3. Follow the Enterprise Tier guides to connect version control, ticketing, messaging, and cloud accounts.

Once installed, you can onboard additional organisations or sandboxes by repeating the same process.

{% hint style="info" %}
There is no `.nullify.yml` or other in-repo config file to add. Behaviour such as the CI/PR gate (fail-build, automated PR reviews), ticket-creation thresholds, SLA policies, finding allowlists, and notification targets is configured in the Nullify dashboard under **Configure** (or via the admin API), not by committing a file to your repository.
{% endhint %}


# Onboarding

After your Nullify tenant is provisioned, complete the onboarding flow to connect your codebases, connectors, cloud accounts, and configure how Nullify understands your business context.

## Onboarding Flow

The onboarding wizard guides you through a sequence of configuration steps. Each section teaches Nullify about your environment so it can triage findings, validate exploitability, and route work to the right teams.

### 1. Welcome

![Welcome Screen](/files/rNUZ3NMzUcLD2GyIEbab)

Start the onboarding process. Onboarding takes less than an hour for most organizations.

### 2. Connect Codebases

![Connect Codebases](/files/9d5EIyam5GCMCFCrfGGV)

Connect Nullify to your source control:

* **GitHub** - Install the Nullify GitHub App
* **Bitbucket** - Install the Nullify Bitbucket App
* **GitLab** - Connect via OAuth ([GitLab install guide](/enterprise-tier/gitlab-install))

Nullify needs repository access to review code, open pull requests, and map ownership from CODEOWNERS files.

### 3. Connect Workflow Tools

![Connect Workflow Tools](/files/lk2jYH5gYd1sSKHY49V5)

Connect collaboration and assignment tools:

* **Jira** - Auto-assign tickets, track remediation, create issues
* **Compass** - Map services to teams for improved ownership
* **Slack** - Send notifications, escalate findings, collaborate on triage
* **Microsoft Teams** - Alternative to Slack for notifications

These connectors let Nullify work in the tools your team already uses, so much of the day-to-day review and remediation happens where you already are. The Nullify dashboard remains available for configuration, reporting, and deeper investigation.

### 4. Integrate Cloud (AWS Setup)

![Cloud Integration - Setup](/files/rsmRXx02z3Vjk18IeDz4)

Configure cloud access for continuous audits. Nullify provides CloudFormation templates with least-privilege IAM roles.

Copy the External ID, Nullify Role ARN, S3 Bucket name, and KMS Key ARN to use in your AWS account.

### 4b. Integrate Cloud (GCP Setup)

For Google Cloud Platform, Nullify uses OIDC + Workload Identity Federation — no service account keys, no long-lived secrets, revoke any time with `terraform destroy`. The trust is pinned per-tenant so even a compromised Nullify signing key cannot cross tenants.

Copy your **Nullify Tenant ID** from the trust setup tab. The **OIDC Issuer URL** is a fixed Nullify constant — `https://gcp.nullify.ai` — and is the same for every tenant, so there is no per-tenant value to copy. Pass the tenant ID and issuer URL to the [Nullify Cloud Connector Terraform module](https://github.com/Nullify-Platform/nullify-cloud-connector/tree/main/gcp-integration-setup/terraform). Paste the module's `service_account_email` and `workload_identity_provider` outputs back into the console, then click Save.

See the [GCP Connector Setup Guide](/connectors/gcp/configuration) for prerequisites, scope choices (org / folder / projects), and troubleshooting.

### 5. Integrate Cloud (Add Accounts)

![Cloud Integration - Add Accounts](/files/EknbbufFarXEXDx9q4vo)

Add AWS account IDs (12 digits or comma-separated list) — or GCP project IDs in the GCP tab — to enable cloud audits across your infrastructure.

Nullify continuously monitors deployed resources for misconfigurations and maps findings back to repositories.

### 6. Bug Bounty Program

![Bug Bounty Configuration](/files/nvQVKc96XqDi6BgMu4ll)

Configure bug bounty program integration:

* **HackerOne** - Import vulnerability disclosures
* **BugCrowd** - Sync external findings
* **Website URL** - Link to your bounty program
* **Document Based** - Upload scope documents
* **No Program** - Skip if not applicable

Nullify correlates external bug bounty findings with internal code reviews and pentests.

### 7. Configure Asset Scope

![Asset Scope Configuration](/files/2rCazIKOwbg9p4jcIS9u)

Define attack surface monitoring scope:

* **In-Scope Assets** - Domains, IPs, and CIDR ranges Nullify should monitor
* **Excluded Assets** - Assets to ignore (third-party services, partner networks)

Nullify uses this for attack surface discovery, continuous monitoring, and vulnerability assessment prioritization.

### 8. Configure Credentials

![Credentials Configuration](/files/xRrsSgnPTgypjo5TktBC)

Add authentication credentials for deeper pentesting:

* Test credentials for comprehensive authenticated testing
* Service account credentials for API testing
* Admin/user credentials for authorization testing

Nullify validates credentials are not leaked and uses them for authenticated vulnerability validation.

### 9. Configure Applications

![Applications Configuration](/files/XFL9hHRJ2SefKeRaGvES)

Set up applications for continuous pentesting:

* Add application names and URLs
* Associate user credentials for authenticated testing
* Define which apps receive continuous penetration testing

Nullify pentests these applications continuously, replaying authenticated flows and chaining exploits.

### 10. Set SLAs

![SLA Configuration](/files/9AjNCnbiKuHpuA3nf5pe)

Define remediation timelines by priority:

* **Urgent** - Immediate attention (e.g., 7 days)
* **Important** - High-priority issues (e.g., 21 days)
* **Medium** - Standard remediation timelines (e.g., 60 days)

Nullify uses these SLAs to prioritize work and escalate overdue findings.

### 11. Memory Vault

![Memory Vault](/files/bzQwO8do8pbrVsFr7rWB)

Upload organizational context documents:

* Architecture diagrams
* Incident reports
* Penetration test reports
* Vulnerability management documentation

Nullify learns from these documents to better understand your risk profile and prioritize findings.

### 12-17. Onboarding Questionnaire

Nullify asks about your organization to understand business context:

**General Information** ([Screenshot](https://github.com/Nullify-Platform/nullify/tree/main/public-docs/.gitbook/assets/onboarding/12-questionnaire-general-company-info.png))

* Company name
* Core products and services

**Sensitive Data Types** ([Screenshot](https://github.com/Nullify-Platform/nullify/tree/main/public-docs/.gitbook/assets/onboarding/13-questionnaire-sensitive-data-types.png))

* PII, PHI, Payment Information, Financial Information, Trade Secrets, Government Data
* Custom data classifications

**Compliance Requirements** ([Screenshot](https://github.com/Nullify-Platform/nullify/tree/main/public-docs/.gitbook/assets/onboarding/14-questionnaire-compliance-requirements.png))

* PCI DSS, HIPAA, SOC1, GDPR, CCPA, DORA, FISMA
* Custom compliance frameworks

**Impact Scenarios** ([Screenshot](https://github.com/Nullify-Platform/nullify/tree/main/public-docs/.gitbook/assets/onboarding/15-questionnaire-impact-scenarios.png))

* Sensitive Data Leaked, Data Loss/Deletion, Tampered Data, System Interruption
* Compromised User Accounts, Unintended User Behavior

**Threat Actors** ([Screenshot](https://github.com/Nullify-Platform/nullify/tree/main/public-docs/.gitbook/assets/onboarding/16-questionnaire-threat-actors.png))

* State Actors, Hacktivists, Insider Threats
* Custom threat profiles

**Additional Context** ([Screenshot](https://github.com/Nullify-Platform/nullify/tree/main/public-docs/.gitbook/assets/onboarding/17-additional-information-optional.png))

* Any other information Nullify should know about your application security posture

### 18. Review and Submit

![Ready to Finish](/files/CDJDAdxxDxmu9auV8F8Q)

Review all configuration before submitting. Once submitted, Nullify's AI agent begins processing your environment.

### 19. Onboarding Complete

![Onboarding Complete](/files/1wXlPYAjVGGPcfzTDbnN)

Nullify is now learning your environment. The platform will:

* Index repositories and build context
* Map team ownership from CODEOWNERS and service metadata
* Begin continuous code reviews on new commits
* Start cloud audits of deployed resources
* Initialize threat intelligence monitoring

You can edit the onboarding questionnaire anytime to refine Nullify's understanding.

## What Happens Next

After onboarding, Nullify operates autonomously:

1. **Context Collection** - Nullify builds a knowledge graph of your services, dependencies, infrastructure, and teams
2. **Continuous Assessment** - Code reviews run on every commit, cloud audits monitor infrastructure changes, pentests validate findings
3. **Triage & Validation** - Findings are ranked by exploitability with evidence, not just theoretical risk
4. **Remediation** - Nullify drafts fixes, opens PRs, keeps them green through CI and review feedback, and assigns the right reviewer to merge
5. **Learning** - Every interaction refines Nullify's understanding of your risk tolerance and business priorities

Much of the work surfaces directly in the tools your team already uses — your source control (GitHub, GitLab, Bitbucket, and Azure DevOps), Jira, Slack, Microsoft Teams, and your cloud accounts (AWS and GCP) — while the Nullify dashboard remains available for configuration, reporting, and deeper investigation.

## Need Help?

* Reach out to your Nullify customer success manager
* Email <support@nullify.ai>
* See [Install](/getting-started/install) for tenant provisioning details


# Context Engine

## Overview

The Context Engine is the knowledge layer that every other Nullify capability reads from. It continuously builds a unified model of your environment — the code you ship, the cloud you run it on, the dependencies you pull in, and how all of it connects — so that detection, triage, and remediation can reason about real-world risk instead of isolated findings.

Where a scanner sees a single misconfiguration or vulnerable package, the Context Engine sees the application it belongs to, whether it is reachable from the internet, who owns it, and what data flows through it. That shared context is what lets Code Reviews, Cloud Audits, Pentests, and Security Program Management agree on what actually matters.

The Context Engine builds and serves five major bodies of context:

* **Asset Graph** — a unified, queryable graph of every cloud, cluster, repository, and application asset, with the relationships and reachability between them.
* **SBOM & Dependency Graph** — generated software bills of materials and a tenant-wide dependency graph with exposure windows.
* **Cloud Recon** — AI-driven reconnaissance of each cloud account: topology, IAM posture, network exposure, trust boundaries, and attack-path hotspots.
* **Dependency Exposure** — point-in-time and historical answers to "where is this package, at this version, deployed across my estate?".
* **Memory Vault** — durable agent memory plus a tenant document vault that grounds every agent in your organization's specifics.

## Asset Graph

The Asset Graph merges infrastructure and repository context into one connected model of your estate. Cloud accounts, Kubernetes clusters, repositories, and application groupings become nodes, and the relationships between them — deployment, network paths, ownership — become edges.

The graph powers the platform's asset explorer and is queryable end to end:

* **Landing & summary** give per-kind node counts and entry-point cards (cloud accounts, clusters, applications, repositories) for navigating the estate, plus presigned download URLs for the combined infrastructure and repo-context graphs.
* **Search & subgraph** let you find nodes by query string and extract a bounded subgraph around any starting node.
* **Reachability** computes whether a node is reachable from the internet by walking allowed edge types from a synthetic Internet node toward the target, returning the path(s) it found. When the graph is too large to fully enumerate, the result is explicitly marked as undetermined rather than falsely reporting "not reachable" — so consumers never mistake an incomplete search for a safe asset.

Reachability and ownership from the Asset Graph are what let other capabilities prioritize. A vulnerable dependency on an internet-facing service ranks above the same dependency buried in an internal batch job, because the graph knows the difference.

### Asset Inventory

On top of the graph, the Context Engine maintains a categorized **Asset Inventory** — a flat, filterable list of assets grouped into categories such as compute, container, cluster, serverless, network, connectivity, load balancer, CDN and DNS, storage and backup, database, messaging, data pipelines, identity, security, observability, CI/CD, pipelines, AI/ML, artifacts and artifact repositories, repositories, and projects. Each asset can carry tags and flags for routing and triage, and a dedicated public-asset view surfaces everything exposed to the internet.

{% hint style="info" %}
The Asset Graph is built automatically from your connected cloud accounts and repositories. There is nothing to configure per asset — connect a source and its assets flow into the inventory and graph. See the [connector guides](/connectors/aws) for setup.
{% endhint %}

## SBOM & Dependency Graph

The Context Engine generates a software bill of materials (SBOM) for every repository and project, then assembles those SBOMs into a tenant-wide dependency graph.

SBOM generation runs a full pipeline — clone the repository, classify each project, and resolve its dependency tree. Projects with their own lockfile are resolved precisely; projects without a lockfile fall back to the repository or workspace root, and the engine tracks which projects were resolved without a lockfile so you can see where dependency resolution is least precise. SBOMs are stored per repository, project, and commit, and the engine returns the latest SBOM for a repository, a specific repository/project SBOM, or a get-or-generate result that produces one on demand.

From the assembled graph you can:

* Browse the SBOM **tree** for the whole tenant.
* Resolve dependencies **by BOM reference** and list everything that **depends on** a given component (its dependents / parent chains).
* See which projects and repositories were **resolved without a lockfile**.

Generated SBOMs are the substrate for Dependency Analysis and the dependency-exposure queries below, and they feed the supply-chain context that Pentests and Cloud Audits draw on. SBOMs for a repository are also retrievable through the SCA API (`GET /sca/repositories/{repositoryId}/sbom`).

## Cloud Recon

Cloud Recon is the Context Engine's AI-driven reconnaissance of each connected cloud account. Nullify analyzes each account's infrastructure in parallel, then synthesizes the results — including cross-account IAM trust — into a single artifact per account and an organization-wide rollup.

For each account, Cloud Recon produces a structured synthesis covering:

* **Topology & overview** — the account's resource topology and a high-level summary.
* **Security boundaries & trusted services** — where the account's trust edges sit and which external services it trusts.
* **Network exposures** — internet-facing and over-broad network paths.
* **Traffic flows** — observed and inferred flows between resources.
* **IAM posture** — effective-permission resolution and privilege-escalation paths.
* **Cross-account relationships & tenant-wide trusts** — how accounts trust one another.
* **Hotspots** — a ranked list of attack-path concentrations.
* **Graph gaps** — places where the engine could not fully resolve the picture, surfaced honestly rather than hidden.

The latest synthesis is available per account and at the organization level, alongside the latest scan metadata. Cloud Recon enriches the Asset Graph and gives Cloud Audits the infrastructure understanding it needs to separate real risk from benign configuration variation.

## Dependency Exposure

When a new CVE drops or a malicious package is disclosed, the first question is "do we use it, and where?". Dependency Exposure answers that against the dependency graph, with version-aware and time-aware precision.

* **Active dependencies** lists what is currently active across every repository and project, with an aggregate summary including counts and the oldest active dependency.
* **Exposure by version** returns the exposure windows — across the whole tenant or scoped to one repository/project — that intersect a version filter expressed as a semver range or a content hash. This is the "who is running log4j between 2.0 and 2.14.1" query.
* **Exposure history** returns the windows over which a given package (by ecosystem and name) was present, so you can answer "how long were we exposed?".
* **Point-in-time and diff** queries return the active dependencies at a specific commit, or the dependency delta between two commits, for a repository/project.

Because exposure is computed from generated SBOMs and stored as windows over commit history, answers reflect not just the present state but the full timeline of when a component entered and left your estate.

## Memory Vault

The Memory Vault is two complementary stores that keep Nullify's agents grounded in your organization.

**Agent memory** is a first-class, versioned store of what the platform has learned about your environment — durable facts, decisions, and context that agents accumulate over time. Memories are typed, can be associated with documents, retain version history, and are searched with **hybrid semantic + lexical search** so an agent can recall the most relevant prior context for the task in front of it.

**The document vault** lets you upload organization-specific files — architecture notes, runbooks, security policies, and other context — that ground agents in how your organization actually operates. Files are uploaded through presigned URLs, listed and retrieved on demand, and an onboarding step ingests them into the knowledge base so agents can draw on them during triage, planning, and remediation.

Together with the **organization context** — your classification, technology profile, and risk posture — the Memory Vault is how Nullify avoids generic, context-free advice. It is also part of the same knowledge model Pentests exploit and Security Program Management reasons over when assigning and sequencing work.

## How the Pieces Connect

The Context Engine is read by every detection and remediation capability:

* **Code Reviews** use repository context, ownership, and the dependency graph to prioritize and route findings.
* **Cloud Audits** use Cloud Recon and the Asset Graph to validate whether an infrastructure issue is a real risk in context.
* **Pentests** exploit the knowledge model — endpoints, reachability, and discovered credentials — to drive deep, application-aware testing.
* **Security Program Management** reads ownership, capacity, and organizational risk to assign the right work to the right people.

## API Access

The Context Engine's model is queryable for automation and reporting. Endpoints live under the `/context/*` namespace (with asset-graph queries at `/context/asset-graph/*` and infrastructure graphs at `/context/infrastructure/*`). Representative endpoints:

| Area                | Endpoint                                                         | Purpose                                                    |
| ------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------- |
| Asset Graph         | `GET /context/asset-graph/summary`                               | Unified summary of infrastructure and repo-context graphs. |
| Asset Graph         | `GET /context/asset-graph/reachability`                          | Compute whether a node is reachable from the internet.     |
| Asset Inventory     | `GET /context/asset-inventory/public`                            | List every asset exposed to the internet.                  |
| SBOM                | `GET /context/sboms/repository/{repositoryId}/latest`            | Latest SBOMs for a repository.                             |
| SBOM                | `GET /context/sboms/tree`                                        | Tenant-wide SBOM tree.                                     |
| Cloud Recon         | `GET /context/cloud-recon/accounts/{accountId}/synthesis/latest` | Full reconnaissance synthesis for an account.              |
| Dependency Exposure | `GET /context/deps/exposure`                                     | Tenant-wide package exposure by semver or hash.            |
| Dependency Exposure | `GET /context/deps/active`                                       | Active dependencies across all repositories/projects.      |
| Memory Vault        | `POST /context/memories/search`                                  | Hybrid semantic + lexical search over agent memory.        |
| Memory Vault        | `GET /context/vault/files`                                       | List uploaded organization documents.                      |
| Applications        | `GET /context/applications`                                      | The catalog of applications and their context.             |

All requests use a service-account token. See the [API Reference](/api-reference/api-reference) and the full [OpenAPI specification](/api-reference/api-reference/openapi-spec) for the complete contract.

## Configuration

The Context Engine builds itself from your connected sources — there is nothing to enable per asset. Connect your repositories and cloud accounts during [Onboarding](/getting-started/getting-started), and the asset graph, SBOMs, cloud recon, and dependency graph populate automatically as scans run. See the connector guides for setup: [AWS](/connectors/aws), [GCP](/connectors/gcp).


# Code Reviews

## Overview

Nullify reviews code across three layers: application-code vulnerabilities, dependency vulnerabilities, and leaked secrets. Application-code and dependency review run on pull requests and on the default branch, while secrets are checked on every commit across every branch — keeping security issues from reaching production.

## Scanning Scope

### Application Code Analysis

* **Pull Requests**: Reviews PRs targeting the default branch
  * Reviews the changed code in the diff
  * Posts inline review comments on findings
  * Reports a **Nullify** check whose pass/fail is [configurable](/configuration/configuration-pull-requests) — informational by default, or a required gate on net-new findings
* **Default Branch Commits**: Reviews all commits pushed to the default branch
  * Full repository review on every push
  * Updates the dashboard with the complete inventory
  * Creates tickets and notifications for new findings
* **Other Branches**: Not reviewed for code vulnerabilities

### Dependency Analysis

* **Pull Requests**: Reviews dependency changes in PRs targeting the default branch
  * Reviews changed manifest and lock files (`package.json`, `pom.xml`, `Cargo.toml`, etc.)
  * Contributes to the same tenant-configurable **Nullify** check gate
  * Identifies both direct and transitive dependencies
* **Default Branch Commits**: Full dependency review on every push
  * Analyzes all package manifest and lock files
  * Updates the dashboard with the complete vulnerability inventory
  * Drafts autofix PRs for upgradeable dependencies
* **Container Images**: Reviews container images for vulnerable packages

### Secrets Detection

* **All Branches, All Commits**: Reviews every commit on every branch
  * Diff-scoped review
  * Notifies the commit author when secrets are found
  * Actively verifies detected credentials
  * Keeps secrets from entering git history
* **Default Branch**: Full git-history review on merge
  * Updates the dashboard with a repository-wide secret inventory

## What Nullify covers

* **Languages & frameworks** — a wide range of languages plus infrastructure-as-code. See [Supported Languages](/capabilities/code-reviews/code-analysis/supported-languages).
* **Dependency ecosystems** — npm, Maven, Go, PyPI, Cargo, and more, with reachability on supported languages. See [Supported Ecosystems](/capabilities/code-reviews/dependency-analysis/supported-ecosystems).
* **Weakness classes** — OWASP Top 10 and CWE Top 25 vulnerability classes, plus compound attack paths. See [Supported Weaknesses](/capabilities/code-reviews/code-analysis/supported-weaknesses).

## Reachability & Exploitability Analysis

Nullify doesn't just report what a scanner matched — it reasons about whether a finding actually matters.

### Code Analysis

* AI-driven triage analyzes how code is used in context.
* Screens out false positives before they reach you.
* Severity reflects exploitability, not just theoretical risk.

### Dependency Analysis

* **Direct vs. Transitive**: marks whether the vulnerability is in a direct dependency.
* **Reachability Analysis**: on supported languages, traces whether a vulnerable package is actually reached by your code.
* Transitive and unreachable vulnerabilities are prioritized lower by default.

### Secrets Verification

* **Active validation**: checks discovered credentials against the real provider.
* Tests API keys against actual services and validates token formats and expiration.
* Not just pattern matching — functional testing. See [Secrets Detection](/capabilities/code-reviews/secrets-detection).

Findings from all three layers are [triaged](/capabilities/triage) together, then routed and remediated through [Security Program Management](/capabilities/program-management) and [Remediations](/capabilities/remediations).


# Code Analysis

## Overview

Nullify analyzes first-party application code and infrastructure to find vulnerabilities before they reach production. It reviews every commit on your default branch and every pull request to catch issues early.

Nullify uses both top-down and bottom-up analysis to expose vulnerabilities:

* **Top-down analysis** traces user input (HTTP requests, API calls, file uploads) through your application logic to identify unsafe data flows
* **Bottom-up analysis** works backward from vulnerable operations (SQL queries, OS commands, serialization) to determine if untrusted data can reach them

This dual approach catches injection flaws, authentication bypasses, authorization failures, business logic bugs, and other code-level vulnerabilities that static patterns alone would miss.

Nullify also scans infrastructure-as-code (Terraform, CloudFormation, Kubernetes manifests, etc.) for misconfigurations that expose services, weaken access controls, or introduce compliance violations.

## Coverage

**Application Code**

* Injection flaws (SQL, NoSQL, command, LDAP, XPath, template)
* Authentication and authorization failures
* Broken access control (IDOR, privilege escalation)
* Business logic flaws (race conditions, workflow bypass, price manipulation)
* Unsafe deserialization
* SSRF and XXE vulnerabilities
* Sensitive data exposure and logging

**Infrastructure-as-Code**

* Exposed services and overly permissive network rules
* Weak IAM policies and overprivileged roles
* Misconfigured secrets management
* Compliance violations (encryption, retention, logging)


# Supported Languages

## Application Languages

Nullify reviews code in the languages below. Where a finding can be safely patched, Nullify drafts an [autofix](/capabilities/remediations) pull request for it; autofix eligibility depends on the specific finding and its context, not on the language.

| Language   | Coverage Highlights                                                         |
| ---------- | --------------------------------------------------------------------------- |
| C          | Buffer and memory-safety issues with contextual call-tree analysis.         |
| C++        | Includes modern C++ (17/20) with templated sinks.                           |
| C#         | ASP.NET MVC and API patterns.                                               |
| Go         | SQL injection, SSRF, auth bypass, and command injection.                    |
| Java       | Spring/Spring Boot, Jakarta EE, and servlet stacks with dataflow reasoning. |
| JavaScript | Node.js backends, Express, Next.js, and browser contexts.                   |
| TypeScript | Shares the JavaScript analysis engine.                                      |
| Kotlin     | JVM and Android server-side use cases.                                      |
| PHP        | Laravel, Symfony, and raw PHP entry points with sink-driven reasoning.      |
| Python     | Django, Flask, FastAPI, and data pipelines with reachability checks.        |
| Ruby       | Rails and Sinatra patterns, including mass-assignment and CSRF.             |
| Rust       | Memory-safety, unsafe-block, and web-framework (Axum, Actix) analysis.      |
| Scala      | Play and Akka HTTP services.                                                |
| Solidity   | Smart-contract checks (reentrancy, access control, unchecked calls).        |
| Apex       | Salesforce Apex SOQL injection, CRUD/FLS, and sharing-violation analysis.   |
| Swift      | iOS and server-side Swift entry points with sink-driven reasoning.          |

## Infrastructure & Build Definitions

| Format                         | Assessment Coverage | Notes                                                                                      |
| ------------------------------ | ------------------- | ------------------------------------------------------------------------------------------ |
| Terraform (HCL)                | Yes                 | AWS S3, IAM, VPC, EKS, and encryption policies with blast-radius analysis.                 |
| AWS CloudFormation (YAML/JSON) | Yes                 | Reviews stack defaults, logging, encryption, and network exposure controls.                |
| Azure Bicep                    | Yes                 | Focus on AKS, public ingress, identity, and storage hardening.                             |
| Kubernetes Manifests           | Yes                 | Detects run-as-root, privilege escalation, network policy drift, and missing telemetry.    |
| Container build files          | Yes                 | Covers `Dockerfile`/`Containerfile` hardening, multistage hygiene, and base image posture. |

Infrastructure findings link back to cloud context collected through the AWS connector so teams immediately understand affected accounts and services.


# Supported Weaknesses

## Application CWE Coverage

| CWE     | Weakness                          | Coverage Highlights                                                                                                              |
| ------- | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| CWE-20  | Improper Input Validation         | Identifies missing validation, untrusted deserialisation, and unsafe type coercion before data reaches sensitive sinks.          |
| CWE-22  | Path Traversal                    | Tracks file-system access from HTTP handlers, CLIs, and background jobs to prevent arbitrary file reads and writes.              |
| CWE-77  | Command Injection                 | Detects unsanitised command construction in shell runners, build tooling, and deployment scripts.                                |
| CWE-78  | OS Command Injection              | Finds user-controlled data reaching operating system calls (for example `exec`, `Runtime.exec`, `subprocess`, `ProcessBuilder`). |
| CWE-79  | Cross-Site Scripting (XSS)        | Covers reflected and stored variants across templating engines and SPA frameworks.                                               |
| CWE-89  | SQL Injection                     | Supports ORM and raw-query patterns in Go, Java, JavaScript/TypeScript, Ruby, PHP, Python, and Scala.                            |
| CWE-94  | Code Injection                    | Flags dynamic evaluation, template injection, and remote code download scenarios.                                                |
| CWE-269 | Improper Privilege Management     | Verifies authorisation guards, middleware ordering, and access control annotations.                                              |
| CWE-287 | Improper Authentication           | Detects missing authentication, weak session handling, and uncontrolled login backdoors.                                         |
| CWE-352 | Cross-Site Request Forgery        | Validates CSRF mitigations for web, API, and mobile backend endpoints.                                                           |
| CWE-434 | Unrestricted File Upload          | Ensures upload handlers enforce type, size, and storage-location controls.                                                       |
| CWE-502 | Deserialisation of Untrusted Data | Flags unsafe use of native, JSON, XML, and binary deserialisers across languages.                                                |
| CWE-798 | Hard-coded Credentials            | Surfaces embedded secrets in application code alongside Secrets & Data Guardrails detections.                                    |
| CWE-918 | Server-Side Request Forgery       | Detects pivot chains that reach cloud metadata services, internal control planes, or partner APIs.                               |

## Compound Attack Paths

Beyond single-CWE detections, Nullify correlates findings across layers to surface chained, real-world attack paths:

* **Cloud-to-code pivots** — links an infrastructure-as-code misconfiguration to the vulnerable application route it exposes, so an internet-facing service with a reachable injection flaw is prioritized over the same flaw buried in an internal job.
* **Network reachability** — reasons over VPC, security-group, NACL, and gateway configuration to determine whether a vulnerable asset is actually reachable from the internet, feeding Cloud Audit prioritization.
* **Container and build hardening** — catches Dockerfile and container-image misuse, image-provenance gaps, and privilege-escalation vectors.
* **API-aware testing** — imports API specifications, explores authenticated flows, and records reproduction evidence for Bug Hunts and Pentests.


# Dependency Analysis

## Overview

Nullify continuously monitors every package dependency for known CVEs and determines how risky they are within the context of your application and where it runs. It reviews all packages on your default branch periodically to catch new CVEs as they emerge, and analyzes every pull request to prevent new CVEs from being introduced. This combines vulnerability detection with application context to separate noise from genuine risk.

## What It Delivers

* **Manifest discovery** across `go.mod`, `package.json`, `pnpm-lock.yaml`, `yarn.lock`, `bun.lock`, `pom.xml`, `build.gradle`, `libs.versions.toml`, `requirements.txt`, `pyproject.toml`, `conda-lock.yml`, `Gemfile`, `.csproj`, `composer.json`, `Cargo.toml`, and more.
* **Enriched advisories** that blend NVD, OSV, vendor feeds, and Nullify intelligence to calculate severity, exploitability, and affected services.
* **Reachability reasoning** analyse how your programs use the vulnberable code in the dependency, not just whether it is called, and how exposed it is in the infrastructure.
* **Comprehensive triage** compute a risk score comprised of technical and business context
* **Automated remediation** for Go modules, npm/pnpm, Maven, Gradle, PyPI, Poetry, Composer, Bundler, and NuGet with changelog summaries and optional PR creation.

See [Supported Ecosystems](/capabilities/code-reviews/dependency-analysis/supported-ecosystems) for per-package manifest details and for the agent-drafted autofix tiers that cover every other ecosystem (Bun, Cargo, uv, Conda, Yarn, Pipenv, Swift PM, Dart/Flutter, and Elixir Mix).


# Supported Ecosystems

## Overview

Nullify discovers manifests automatically and keeps the following ecosystems up to date. “Autofix” indicates whether Nullify can draft upgrade pull requests for that manifest, and how: **Yes** drafts natively through GitHub's Dependabot updater; **Agent (verified)** drafts through Nullify's AI agent, which regenerates and verifies the lockfile with the ecosystem's own toolchain in a sandbox; **Agent (manifest edit)** drafts through the same agent but edits the manifest without an in-sandbox lockfile verification step. “Reachability” shows where we trace exploit paths through application code before alerting.

| Ecosystem         | Manifest Files                                                  | Autofix Support       | Reachability |
| ----------------- | --------------------------------------------------------------- | --------------------- | ------------ |
| Bun               | `bun.lock`                                                      | Agent (verified)      | No           |
| Cargo (Rust)      | `Cargo.toml`, `Cargo.lock`                                      | Agent (verified)      | No           |
| Composer (PHP)    | `composer.json`, `composer.lock`                                | Yes                   | No           |
| Go Modules        | `go.mod`, `go.sum`                                              | Yes                   | Yes          |
| Gradle            | `build.gradle`, `gradle.lockfile`, `libs.versions.toml`         | Yes                   | No           |
| Maven             | `pom.xml`                                                       | Yes                   | Yes          |
| npm / pnpm        | `package.json`, `package-lock.json`, `pnpm-lock.yaml`           | Yes                   | Yes          |
| Yarn              | `yarn.lock`                                                     | Agent (verified)      | No           |
| NuGet (.NET)      | `.csproj`, `deps.json`, `packages.config`, `packages.lock.json` | Yes                   | No           |
| Pipenv            | `Pipfile`, `Pipfile.lock`                                       | Agent (manifest edit) | No           |
| Poetry            | `poetry.lock`                                                   | Yes                   | No           |
| PyPI              | `requirements.txt`                                              | Yes                   | Yes          |
| PyPI (uv-managed) | `pyproject.toml`                                                | Agent (verified)      | Yes          |
| Conda             | `environment.yml`, `conda-lock.yml`                             | Agent (manifest edit) | Yes          |
| RubyGems          | `Gemfile`, `Gemfile.lock`                                       | Yes                   | No           |
| uv                | `uv.lock`                                                       | Agent (verified)      | No           |
| Swift PM          | `Package.swift`, `Package.resolved`                             | Agent (manifest edit) | No           |
| Dart / Flutter    | `pubspec.yaml`, `pubspec.lock`                                  | Agent (manifest edit) | No           |
| Elixir (Mix)      | `mix.exs`, `mix.lock`                                           | Agent (manifest edit) | No           |


# Secrets Detection

## Overview

Nullify prevents credential leaks and sensitive data exposure from entering your repositories as a core part of Code Review. It identifies hardcoded credentials and sensitive data that should never be in version control, then validates whether those credentials are actually live and exploitable. This combines deterministic pattern matching with NLP-based detectors and real-world validation—catching not just secrets that look dangerous, but secrets that pose an actual risk.

## Detection Highlights

* **Cloud & infrastructure keys** — AWS access keys, Azure storage tokens, GCP API keys, IBM COS, HashiCorp Terraform passwords, DigitalOcean and SoftLayer credentials.
* **Developer tooling** — GitHub personal access tokens, npm/yarn tokens, Slack webhooks, SendGrid API keys, Stripe, Square, Artifactory, Docker Hub, and more.
* **Authentication artefacts** — OAuth client secrets, JWTs, basic-auth credentials, and session identifiers.
* **Sensitive data** — Email addresses, phone numbers, addresses, dates of birth, US Social Security Numbers, and payment card details in English-language datasets. Available to enterprise tenants on request — see [Confidential Data Detection](/capabilities/code-reviews/secrets-detection/confidential-data-detection).

Every alert includes the suspected owner, expiry guidance, and a recommended rotation workflow. Duplicate events are collapsed where rotation tables already show the value.

## Live credential verification

A leaked secret that's already been rotated is noise; a leaked secret that still works is an incident. Nullify tells them apart. When it finds a credential, it checks it against the real provider — across a broad set of services including AWS, GCP, Azure, GitHub and GitLab tokens, Stripe, Slack, and many more — to determine whether it's still live, and captures evidence of what the credential can access. A verified-live secret is prioritized and escalated ahead of one that no longer works, so the most urgent leaks reach the right owner first. This is functional testing, not just pattern matching.


# Supported Secret Categories

## Overview

| Category                            | Example Providers & Tokens                                                                                                                                                | Notes                                                                                                                                    |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| Cloud platforms                     | AWS access keys, Azure Storage SAS tokens, GCP API keys, IBM COS HMAC keys, SoftLayer credentials, HashiCorp Terraform passwords                                          | Alerts include remediation guidance and links to rotation documentation.                                                                 |
| Developer tooling & CI              | GitHub personal access tokens, Bitbucket app passwords, Artifactory API keys, npm access tokens, Slack webhooks, SendGrid API keys, Airtable tokens, Square access tokens | Nullify suppresses known test tokens while flagging production secrets introduced in commits or history.                                 |
| Authentication artefacts            | JWTs, HTTP basic-auth strings, OAuth bearer tokens, webhook signing secrets                                                                                               | Detectors parse headers and base64 payloads to catch embedded credentials.                                                               |
| Generic API keys                    | High-entropy keys that do not match a known provider but behave like credentials                                                                                          | Rules combine entropy thresholds with contextual keywords to minimise false positives.                                                   |
| Personally identifiable information | Email addresses, phone numbers, postal addresses, dates of birth, payment cards                                                                                           | PII detections are explained in [Confidential Data Detection](/capabilities/code-reviews/secrets-detection/confidential-data-detection). |

Have a provider that is not listed? Contact Nullify support to add a managed detector or a custom detection pattern for it.


# Confidential Data Detection

Nullify inspects every diff for sensitive personal information that should never live in source control. When detected, the finding appears in the secrets channel with the relevant PII category and rotation guidance.

## Categories We Classify

* Personal information: email addresses, phone numbers, postal addresses, dates of birth, and US Social Security Numbers.
* Financial information: payment card numbers.
* Location and identity data sourced from structured dumps or seed files.

All detections currently focus on English-language datasets.

## Example Workflow

1. A developer commits a seed file containing realistic user records (`create.sql`).
2. Nullify analyses the diff, labels the exposure as **Personal Information**, and posts the finding to the dashboard.
3. The reviewer can remediate, suppress with justification, or convert the event into a campaign task.

![Sample SQL seed file](/files/8RokOd71wJ1yIb5Q6hup) ![Example PII alert](/files/r6AbpTP3eHeSP4NaEqzb)

> 💡 PII detection is available to enterprise tenants on request. Contact your Nullify representative to enable it for your environment.


# Cloud Audits

## Overview

Nullify continuously audits your cloud infrastructure to detect security weaknesses and misconfigurations. It understands the business context of each workload—what data it processes, who accesses it, and where it runs—then validates whether infrastructure issues pose a real risk through active testing and analysis. This context-driven approach separates genuine security issues from harmless configuration variations.

## How It Works

1. **Cloud Resource Inventory**: Nullify's context engine inventories your cloud accounts
2. **Automatic Trigger**: Whenever an account's inventory completes, Cloud Audits runs against the fresh picture
3. **Infrastructure Analysis**: All resources are evaluated for security weaknesses and misconfigurations
4. **Workload Context**: Nullify correlates resources to the applications they support, understanding business criticality and data sensitivity
5. **Risk Validation**: Active testing and analysis determines whether each finding represents a genuine risk or a benign configuration
6. **Notification & Remediation**: Real risks flow into Jira, Slack, and GitHub with ownership mapping and remediation guidance. Where a misconfiguration maps to an infrastructure-as-code change, Nullify can draft an [autofix](/capabilities/remediations) pull request against the code that provisioned it.

Cloud Audits are **event-driven**: an audit runs whenever an account inventory completes rather than on a fixed daily or weekly schedule, so findings stay in step with changes to your infrastructure.

## Supported Cloud Providers

Nullify audits **AWS, GCP, Azure, and Kubernetes** with multi-account scanning. Coverage spans the core resource categories of each provider, including:

* **Storage & data**: object storage buckets, block volumes and snapshots, managed databases
* **Compute & network**: instances, security groups, load balancers, public exposure
* **Identity & access**: users, roles, policies, key management, password requirements
* **Logging & audit**: audit trails, log integrity, monitoring configuration
* **Kubernetes**: workload, RBAC, and cluster configuration checks

See the connector guides for setup: [AWS](/connectors/aws), [GCP](/connectors/gcp), and [Kubernetes](/connectors/kubernetes). Azure is connected through your Azure account (contact your Nullify representative for setup).

## Security Rules

Nullify ships managed rulesets per provider mapped to compliance frameworks — hundreds of rules each for AWS, GCP, and Azure, plus a dedicated **Kubernetes ruleset**. Rules are grouped by category rather than enumerated individually:

### Storage & Encryption

Detects publicly accessible storage buckets, validates server-side and at-rest encryption, prevents public snapshot exposure, and enforces managed-database encryption.

### Network Security

Identifies public-facing compute, security groups exposing sensitive ports (e.g. SSH/RDP) to the internet, overly permissive default network rules, and publicly accessible databases.

### Identity & Access

Flags root/break-glass access keys, weak password and key-rotation policies, and overly broad IAM permissions.

### Logging & Compliance

Ensures audit logging is enabled, validates log file integrity, and checks monitoring coverage.

### Backup & Recovery

Verifies adequate backup retention and recovery configuration on managed data stores.

## Code-to-Cloud Mapping

Nullify correlates cloud resources back to source code and business context:

* **Repository Links**: Maps resources to the repos that deployed them
* **Ownership Inference**: Uses CODEOWNERS and service metadata to assign findings
* **Workload Context**: Understands what data each resource processes and which applications depend on it
* **Business Impact**: Evaluates risk within the context of data sensitivity, customer exposure, and service criticality

## Compliance Mapping

Every finding includes compliance framework mapping:

* **CIS Foundations Benchmarks** (per provider)
* **PCI-DSS**
* **HIPAA** (select controls)
* **SOC2** (select controls)

## Configuration

Configure cloud access during [Onboarding](/getting-started/getting-started#4-integrate-cloud-aws-setup). Setup is provider-specific — connect each AWS, GCP, or Azure account (and any Kubernetes clusters) with a least-privilege role or service account. See the connector guides for step-by-step instructions: [AWS](/connectors/aws), [GCP](/connectors/gcp).


# Supply Chain Posture Management

## Overview

Supply Chain Posture Management (SCPM) hardens the way your software is built and assembled. Where dependency analysis asks *"does this package have a known CVE?"*, SCPM asks *"is this dependency, manifest, lock file, or CI/CD workflow trustworthy in the first place?"* It reads the manifests, lock files, and pipeline definitions in each repository and flags supply-chain weaknesses — typosquatted or confusable package names, unpinned or floating dependencies, risky install scripts, missing or stale lock files, restricted licenses, and insecure CI/CD configuration — long before any of them turns into a published advisory.

SCPM scans the **default branch** to keep a continuous posture baseline and scans **pull requests** (diff-scoped to the manifests a PR touches) so new supply-chain risk is caught before it merges. Findings are triaged for real-world impact, surfaced in the dashboard and notification targets, and — where a fix can be expressed as a code change — remediated automatically with a pull request.

## How It Works

1. **Manifest & pipeline discovery**: SCPM walks the repository and parses every supported manifest, lock file, and CI/CD definition — npm/`package.json`, Python (`requirements.txt`/`pyproject.toml`/Conda), Go modules, Maven/Gradle (Java), `.csproj` (C#), `Cargo.toml` (Rust), Ruby, Composer (PHP), Dockerfiles and Docker Compose, plus GitHub Actions, GitLab CI, Azure Pipelines, Dependabot, and Renovate config.
2. **Posture detection**: A flat ruleset evaluates each parsed manifest against the supply-chain risk categories below. On a pull request the scan is diff-scoped so only the manifests the PR changed produce findings; on the default branch it runs full-repo.
3. **Triage**: Each detection is enriched with repository and business context, screened for false positives, classified for severity and exploitability, and assigned a priority. Triage combines LLM reasoning with deterministic rules so classification is reproducible.
4. **Delivery**: Real findings are written to the platform, flow into the dashboard, and fan out to Jira, Slack, Teams, and GitHub through the notification configuration, with ownership inferred from CODEOWNERS.
5. **Remediation**: For findings that map cleanly to a manifest edit (for example pinning a floating version or a CI/CD action), SCPM drafts an autofix pull request that matches the surrounding style.

SCPM is **event-driven**: it reacts to scan and finding lifecycle changes as they happen rather than running on a fixed daily or weekly cron, so posture stays in step with changes to your repositories.

## Supply-Chain Risk Coverage

SCPM ships a managed ruleset grouped by category rather than enumerated individually:

### Package Trust

* **Typosquatting** — package names that closely resemble a significantly more popular package, a common malicious-publish vector.
* **Dependency confusion** — internal/private package names that could be shadowed by a public registry package.
* **Supply-chain intelligence signals** — dependency hijacking via git-fork versions and `replace` directives.

### Dependency Pinning & Provenance

* **Unpinned / floating dependencies** — version specifiers that can resolve to different versions across builds.
* **Git/HTTP-sourced dependencies** — packages pulled from a git or HTTP URL instead of a registry, bypassing registry security controls.
* **Missing lock files** — manifests with no lock file, allowing non-deterministic resolution.
* **Unpinned Docker base images** — Dockerfiles and Compose files that reference a tag instead of a digest, exposing builds to tag mutation.
* **Dependency cooldown** — Dependabot ecosystem blocks with no cooldown, or a cooldown shorter than the configured window, that would auto-merge a freshly published (and potentially compromised) version too quickly.

### Install & Build Scripts

* **Install scripts** — packages that execute scripts during installation.
* **Unpinned installs in build scripts** — build steps that fetch or install dependencies without pinning.

### Licensing

* **Missing or restricted licenses** — manifests declaring no license or a license denied by org policy, including copyleft (GPL/AGPL/LGPL/SSPL/EUPL) detection.

### CI/CD Posture

A large, dedicated body of GitHub Actions / GitLab CI / Azure Pipelines checks — unpinned or unverified actions, script and YAML injection, artifact token leakage, excessive or over-provisioned permissions, dangerous triggers, cache poisoning, privileged checkout, Docker-socket exposure, self-hosted-runner risk, OIDC over-scoping, and missing build attestation, among others.

{% hint style="info" %}
Known-malware detection by advisory (OSV/GHSA) is owned by [Dependency Analysis](/capabilities/code-reviews/dependency-analysis), not SCPM. SCPM focuses on *posture and trust signals* that don't yet have a published advisory.
{% endhint %}

## How SCPM Fits With Dependency Analysis

SCPM and dependency analysis (SCA) are complementary halves of dependency security:

* **Dependency Analysis (SCA)** answers *is this package version affected by a known vulnerability?* — it matches your dependency graph against enriched CVE/advisory feeds and reasons about reachability and exposure.
* **SCPM** answers *is the way you consume dependencies and run your pipelines trustworthy?* — typosquats, confusable names, unpinned versions, missing lock files, risky install scripts, license policy, and CI/CD hardening.

A package can be advisory-clean yet still be a supply-chain risk (a typosquat, an unpinned floating range, an install script, a workflow that leaks a token), which is exactly the gap SCPM covers. The two run over the same repositories and the same manifest set, so findings from both land side by side in the dashboard and route through the same triage, ticketing, and remediation flows.

## Finding Lifecycle

Every SCPM detection becomes a finding that moves through a consistent lifecycle:

1. **Detected** — a scan produces a new finding; pull-request scans flag findings the PR introduces.
2. **Triaged** — the finding is screened, classified, and prioritised, and re-triaged if a later scan changes the picture.
3. **Ticketed & notified** — findings that meet your configured thresholds create tickets and notify Slack and Teams.
4. **Remediated** — an autofix produces a fix and, where it applies, opens a pull request.
5. **Resolved or suppressed** — a finding can be allowlisted, un-allowlisted, or archived when it no longer reproduces.

### Querying & acting on findings

The SCPM API mirrors the other detection services:

* `GET /scpm/findings` (plus `/scpm/findings/preview` and `/scpm/findings/detailed`) to list findings.
* `GET /scpm/findings/{findingId}`, `GET /scpm/findings/{findingId}/triage`, and `GET /scpm/findings/{findingId}/events` to inspect a single finding and its history.
* `PATCH /scpm/findings/{findingId}` to update a finding.
* `POST /scpm/findings/{findingId}/ticket` and `DELETE /scpm/findings/{findingId}/ticket` to manage tickets.
* `POST /scpm/findings/{findingId}/allowlist`, `POST /scpm/findings/{findingId}/unallowlist`, and the bulk `POST /scpm/findings/allowlist` to suppress findings.

### Allowlisting a finding

To allowlist (ignore) a finding, send an `allowlistReason` and an `allowlistType`:

```json
{
  "allowlistReason": "Internal package, not a public typosquat",
  "allowlistType": "UserFalsePositive"
}
```

`allowlistType` is one of `AI`, `UserFixed`, `UserAssumeRisk`, `UserFalsePositive`, or `UserOther`. The bulk endpoint additionally takes a `findingIds` array.

{% hint style="info" %}
Findings are allowlisted per-finding from the dashboard or API — there is no file-based or static ignore list.
{% endhint %}

## Remediation (Autofix)

When a finding can be resolved by a manifest or pipeline change, SCPM drafts a fix and opens a pull request through the shared autofix pipeline:

* `POST /scpm/findings/{findingId}/autofix/fix` triggers a fix.
* `POST /scpm/findings/{findingId}/autofix/cache`, `…/cache/diff`, and `…/cache/create_pr` stage and preview a fix before opening a PR.
* `GET /scpm/findings/{findingId}/autofix/status`, `…/state`, and `…/activity` track progress.

Autofix pull-request limits are platform-managed (3 open Nullify fix PRs per repository, 5 new fix PRs per remediation plan) and are not configurable per repository. See [Remediations](/capabilities/remediations) for how fixes work with developers, ownership routing, and CI feedback.

## Real-time reaction

Supply-chain posture stays in step with your repositories: as scans complete and findings move through their lifecycle, notifications, ticketing, and [Security Program Management](/capabilities/program-management) react in real time rather than waiting for a nightly batch.

## Dependency cooldowns

A freshly published package version is a supply-chain risk in its own right — worm-speed compromises spread through new releases before any advisory exists. SCPM can flag direct and dev dependencies that are newer than a configurable recency window (7 days by default), independent of whether any CVE has been filed, and escalates the riskiest cases — such as a version published moments ago, or an ecosystem with no native cooldown primitive.

You control this per tenant through the SCPM settings API (`GET`/`PUT /scpm/settings`): toggle the cooldown gate on or off, set the window in days, and add per-ecosystem overrides.

This complements the narrower **Dependabot cooldown** conformance rule above (which checks that your Dependabot config doesn't auto-merge freshly published versions too quickly).

## Configuration

SCPM settings live in the dashboard and admin API, not in any repository config file:

* **Ticket creation thresholds** (severity/priority per finding type): **Configure → Ticketing**.
* **CI/PR gate** (fail the build, enable PR reviews): **Configure → Pull Requests**.
* **Notification destinations**: **Configure → Connectors** (Slack, Teams, Jira).
* **Dependency cooldown window**: SCPM settings API (`PUT /scpm/settings`).

Ignore decisions are made per-finding (see [Allowlisting a finding](#allowlisting-a-finding)).


# Bug Hunts

## Overview

Nullify continuously watches your external attack surface the way an offensive security engineer would — discovering internet-facing assets, fingerprinting the services running on them, and probing for exposures before an attacker finds them. Bug Hunts combine passive reconnaissance, active service discovery, and protocol-aware analysis, and they lean on everything Nullify already knows about your code, dependencies, and cloud to focus on what actually matters.

Unlike a one-off scan, a Bug Hunt is a standing watch: Nullify re-runs it on a cadence, compares each run against the last, and only surfaces what has **changed**.

## How Bug Hunts run

Bug Hunts run automatically once your external scope is configured — there is nothing to schedule by hand. Nullify runs each hunt on a recurring cadence and paces itself to your program's budget: as the surface stays stable, it settles into a lighter rhythm; when new exposure appears, it tightens back up. You can also trigger a hunt — or a single reconnaissance suite — on demand at any time.

### Change-only findings

Every run is diffed against the previous one. Nullify surfaces findings that are **New** or **Reopened**, and stays quiet about assets that are **Unchanged** or have been **Resolved**. This keeps a Bug Hunt from re-alerting on the same known exposure run after run, so the findings you see are always the delta since you last looked.

Your triage decisions persist across every future run. When you allowlist an asset, archive a finding, override its severity, set a priority, or leave a note, that decision travels forward — a rescan won't undo it or re-raise something you've already judged.

## What Nullify looks for

### Reconnaissance

Nullify maps your attack surface from the outside in:

* **Subdomain discovery** — passive sources such as certificate-transparency logs and DNS records, plus active discovery with adaptive wordlists.
* **Service and port discovery** — low-noise scanning across discovered hosts to find exposed services.
* **Service fingerprinting** — technology-stack and version identification, banner analysis, and HTTP inspection.

### Exposure and misconfiguration checks

Across discovered services Nullify checks for subdomain takeover, exposed or sensitive files, missing or weak transport security and certificate issues, absent security headers, exposed admin or debug interfaces, and other common internet-facing weaknesses. It performs protocol-aware analysis for web, database, infrastructure, container-orchestration, and cloud service endpoints — testing authentication, reviewing configuration, and checking for known weaknesses and weak credentials.

### Context from the rest of Nullify

Bug Hunts are sharper because they draw on the rest of the platform:

* **Code review findings** highlight likely injection points and vulnerable patterns worth probing from the outside.
* **Dependency findings** identify exploitable CVEs on exposed services and inform which exposures matter most.
* **Cloud context** contributes topology, reachability, and code-to-cloud mapping so an exposure can be tied back to the application and blast radius it affects.

## Intensity levels

Intensity controls how much tooling Nullify brings to a hunt:

1. **Low** (default, production-safe) — the minimal set of discovery and enumeration tooling. Conservative, read-only reconnaissance.
2. **Medium** — most of the available tooling, for a deeper assessment.
3. **High** — the full toolset, including heavier, more aggressive techniques such as brute forcing.

Nullify paces its requests conservatively and backs off when a target shows signs of stress, so hunts stay safe to run against production. For full exploit-chain validation with reproduction evidence, use [Pentests](/capabilities/pentests).

## Controls

You stay in control of scope and noise:

* **Scope** — define in-scope assets (domains, IP ranges, CIDR blocks) and exclude assets you don't own or don't want tested (third-party services, partner networks).
* **Per-suite toggles** — enable or disable individual reconnaissance and analysis suites.
* **Severity floors** — set a minimum severity per vulnerability class so low-value classes stay quiet.
* **Per-finding triage** — allowlist, archive, override severity, set priority, or annotate any finding; decisions persist across future runs.

Nullify only tests assets you are authorized to test. Before a hunt runs against a target, ownership must be proven, and out-of-scope targets (loopback, private-network, and cloud-metadata addresses) are rejected.

## Evidence

Bug Hunt findings come with the reconnaissance evidence behind them — the discovered services and versions, open ports, technology fingerprints, and the exposure detail — plus a prioritized path to remediation. Bug Hunts surface and prioritize exposure through reconnaissance; for active exploitation with validated proof, promote a discovered application to a [Pentest](/capabilities/pentests).

## Where findings go

Bug Hunt findings flow into the same places as the rest of Nullify's work: the dashboard, Jira and other ticketing with ownership mapping, Slack and Teams notifications for critical attack-surface changes, and [Security Program Management](/capabilities/program-management) for campaign-driven attack-surface reduction. Discovered applications can be promoted directly into [Pentest](/capabilities/pentests) targets.

## Configuration

Configure Bug Hunts during [Onboarding](/getting-started/getting-started):

* Define in-scope assets (domains, IP ranges, CIDR blocks).
* Specify excluded assets (third-party services, partner networks).
* Link bug bounty program data if you run one.
* Adjust intensity, per-suite toggles, and severity floors.


# Pentests

## Overview

Nullify runs continuous, hands-off penetration tests as a deep assessment of your deployed applications. These tests find authentication, authorization, and business-logic flaws by actively exploiting the weaknesses Nullify already understands from reviewing your code — using the knowledge model it builds from your codebase, dependencies, and cloud. Nullify maps endpoints, replays authenticated flows, crafts payloads informed by code-level context, and validates exploit chains with reproduction evidence.

Where a [Bug Hunt](/capabilities/bug-hunts) watches your external surface for exposure, a Pentest goes deep on a known application and *proves* what's exploitable.

## Triggering Pentests

Pentests run on demand — start one against a specific application whenever you need it, with full control over the scan parameters and results tracked in real time. Teams typically trigger a pentest on new application deployments, major feature releases, [Security Program Management](/capabilities/program-management) campaigns, or to validate a remediation.

## Supported Application Types

Nullify pentests **REST and GraphQL APIs**:

### REST APIs

* OpenAPI/Swagger schema-based testing.
* Automatic schema discovery (checks common locations such as `/openapi.json`, `/swagger.json`, `/api-docs`).
* Fuzzing of path parameters, query strings, and request bodies.
* Intelligent, schema-aware API testing that combines targeted vulnerability probing with Nullify's own reasoning agents.

### GraphQL APIs

* Introspection query analysis.
* Mutation and query testing.
* Schema-based attack generation.
* Authorization-matrix testing across resolvers.

## Authenticated Testing

Nullify supports comprehensive authenticated pentest workflows:

### Authentication Methods

* **Bearer Tokens**: JWT, OAuth2 access tokens
* **Basic Auth**: Username/password pairs
* **OAuth Flows**: Authorization code, client credentials
* **Custom Headers**: API keys, custom authentication schemes
* **Login Flows**: POST to a login endpoint with credentials

### Single-User Testing

Tests authenticated endpoints with a single user context:

```yaml
auth:
  method: "bearer"
  token: "${API_TOKEN}"
  headers:
    X-API-Key: "${API_KEY}"
```

### Authorization Matrix Testing

Tests access control across multiple user roles:

```yaml
auth:
  users:
    - role: "admin"
      method: "bearer"
      token: "${ADMIN_TOKEN}"
    - role: "user"
      method: "bearer"
      token: "${USER_TOKEN}"
    - role: "readonly"
      method: "bearer"
      token: "${READONLY_TOKEN}"
```

**Matrix Analysis**:

* Validates each role can only reach the endpoints it should.
* Detects privilege-escalation opportunities.
* Identifies broken access control (IDOR, vertical/horizontal escalation).
* Tests for missing authorization checks.

### Pre-Authentication Validation

Before testing begins, Nullify:

1. Authenticates all configured users.
2. **Fails fast** if any user cannot authenticate.
3. Confirms tokens/credentials are valid.
4. Maintains authenticated sessions throughout testing.

## Request Chaining & Multi-Stage Exploitation

Nullify validates vulnerabilities through multi-stage exploitation rather than reporting theory:

> This section describes exploit chaining *within* a pentest engagement. It is a different thing from [Exploit Validation](/capabilities/pentests/exploit-validation), the continuous capability that validates individual code findings against your deployed service.

### Stateful Session Management

* Preserves authentication context throughout a chain.
* Tracks session state and cookies.
* Supports multi-step workflows.

### Fuzzing

* Substitutes fuzz tokens across URLs, headers, query params, and bodies.
* Supports custom wordlists for targeted fuzzing.
* Analyzes responses for status, size, and timing anomalies.

### Exploit Chain Execution

Example multi-stage attack:

1. **Endpoint Discovery** → identify API endpoints via schema.
2. **Parameter Fuzzing** → find injection points.
3. **SQL Injection** → extract database credentials.
4. **Lateral Access** → use credentials to reach admin endpoints.
5. **Data Exfiltration** → demonstrate breach capability.

### Response Analysis & Iteration

* Parses responses (status, headers, body).
* Extracts data from responses for subsequent requests.
* Analyzes error messages for information disclosure.
* Adapts strategy based on application behavior.
* Distinguishes a genuine authorization denial from a dead credential, so it never mistakes a broken test setup for a secure endpoint.

Runtimes vary with the size and complexity of the target; deep exploit chains can run for an extended period.

This is the exploitation Nullify performs *within* a single pentest. To prove or disprove an individual code-review or dependency finding on its own — driven by your own policies, without launching a full pentest — see [Exploit Validation](/capabilities/pentests/exploit-validation).

## Vulnerability Coverage

Pentests validate:

* **Injection Flaws**: SQL, NoSQL, Command, LDAP, XPath
* **Broken Authentication**: Weak passwords, session fixation, token manipulation
* **Broken Authorization**: IDOR, privilege escalation, missing access controls
* **Security Misconfiguration**: Debug mode, default credentials, verbose errors
* **Business Logic Flaws**: Price manipulation, workflow bypass, race conditions
* **SSRF**: Server-side request forgery with cloud metadata access
* **XXE**: XML external entity injection
* **File Upload**: Malicious file upload and execution

## Context from Code Review

Pentests are informed by everything Nullify has already learned:

* **Code review findings** — known injection points and vulnerable patterns are prioritized for testing, and code-level context guides payload selection.
* **Dependency findings** — exploitable CVEs (for example Log4Shell) are validated against the live application.
* **Discovered secrets** — credentials Nullify has found are used for authenticated testing, and their scope and validity are exercised.

## Application Management

Every pentest is associated with an **Application**:

**Application Attributes**:

* Name and description
* One or more instances (URLs/endpoints)
* Business criticality level
* Environment (dev, staging, prod)
* Pre-configured credentials
* Scan preferences

**Instance Configuration**:

* Base URL for testing
* API schema location (file path, URL, or auto-discovery)
* GraphQL endpoint (if applicable)
* Authentication requirements

## Evidence & Reporting

Pentest findings include:

* **Request/Response Pairs**: full HTTP traffic demonstrating the vulnerability.
* **Exploit Steps**: detailed reproduction instructions.
* **Impact Analysis**: business impact and blast radius.
* **Fix Guidance**: specific remediation recommendations.
* **Screenshots**: visual proof of exploitation where applicable.
* **Video Recordings**: a walkthrough of the exploit chain where applicable.

Every reported exploit is independently re-reviewed against the recorded evidence before it reaches you, filtering out false positives such as a denied request that merely returned a 200 — so a Pentest finding means Nullify actually reproduced the issue.

## Safety & Rate Limiting

Pentests respect production stability:

* **Adaptive rate limiting** — backs off if the application shows signs of stress.
* **One run per application** — Nullify won't run overlapping scans against the same application.
* **Timeout handling** — degrades gracefully on slow responses.
* **Emergency stop** — aborts on systemic failures (auth expired, target down).
* **Failure distinction** — separates hypothesis failures from environmental issues.

## Where findings go

Pentest findings flow into the dashboard, Jira and other ticketing with severity-based prioritization, Slack and Teams notifications for critical findings, and [Security Program Management](/capabilities/program-management) for campaign-driven remediation. Validated findings are prioritized for [autofix](/capabilities/remediations) where a fix applies.

## Configuration

Configure pentests during [Onboarding](/getting-started/getting-started):

* **Applications**: define target applications and instances.
* **Credentials**: add authentication credentials for testing.
* **Scope**: specify included/excluded endpoints.

## Pentests vs Bug Hunts

| Aspect        | Bug Hunts                                | Pentests                                          |
| ------------- | ---------------------------------------- | ------------------------------------------------- |
| **Scope**     | External attack surface (domains, IPs)   | Known applications (APIs)                         |
| **Discovery** | Subdomain enumeration, service discovery | API schema analysis, endpoint mapping             |
| **Depth**     | Reconnaissance and exposure detection    | Active exploitation with validated proof          |
| **Evidence**  | Service inventory, exposure detail       | Request/response pairs, reproduced exploit chains |
| **Cadence**   | Continuous, change-only surfacing        | On-demand                                         |


# 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). 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)). 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) 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).

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).

## 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.


# Preparing for an Engagement

Everything the pentester needs before a run, and why each item matters. Gathering these first is the difference between a two-hour engagement and a two-week one.

## Checklist

|   | Item                                     | Required             |
| - | ---------------------------------------- | -------------------- |
| 1 | Target hostname and port                 | Yes                  |
| 2 | API specification, or a discoverable one | Strongly recommended |
| 3 | One credential set per role              | Strongly recommended |
| 4 | Nullify source IP allowlisted            | Yes                  |
| 5 | Rate limits relaxed for that IP          | Recommended          |
| 6 | Repository name                          | Recommended          |
| 7 | Agreed test window and scope             | Yes                  |

## 1. Target

A fully-qualified hostname and port, for example `api.staging.example.com:443`.

Test against a staging or QA environment where you can. The pentester sends real attack traffic and may create or modify data.

If the service is not reachable from the internet, see [Running in Private Networks](/capabilities/pentests/private-networks).

## 2. API specification

This matters more than anything else on the list.

Nullify looks for a specification in this order:

1. A file you supply with `--spec-path`.
2. GraphQL introspection, for GraphQL APIs.
3. Common paths on your running service — `/openapi.json`, `/swagger.json`, `/api-docs`, `/graphql`. If your service already exposes one of these, you need to do nothing.
4. A specification reverse-engineered from your source code.
5. A minimal generated fallback.

Testing works without a specification, and the pentester can still send live requests and attempt exploitation. Coverage is much shallower, however, because it lacks the schema browsing and endpoint context used to map the API systematically.

Send OpenAPI or Swagger as either JSON or YAML.

## 3. Credentials

The pentester logs in as each role you give it and replays requests across identities. That is how it finds broken object-level authorization, privilege escalation, and authorization bypass — the flaws that scanners miss and that matter most in an API.

For each role, provide:

* A role name, such as `admin`, `member`, or `guest`
* Username and password, or a token
* The login URL, if login is not a simple token
* The TOTP secret, if multi-factor authentication is enforced on the account

Use dedicated test accounts rather than real user accounts.

Nullify supports form logins with optional TOTP, API keys (in a header, bearer token, query parameter, or JSON body), HTTP basic auth, OpenID Connect, and OAuth 2.0 client-credentials and authorization-code flows.

Secrets are encrypted at rest and are never returned by the API — they read back masked.

**Every credential must authenticate.** A pentest stops at the login stage if any attached credential fails, rather than testing a partial view of your application. Validate each credential in the dashboard, or with `POST /dast/credentials/{credentialId}/validate`, before starting a run.

Validation reports one of three outcomes. `valid` means the login returned real evidence of a session. `invalid` means it was rejected — fix the credential. `unknown` (shown as "Not Tested") means the check could not get an answer: a WAF or bot-mitigation product answered on your application's behalf, the endpoint rate-limited the check, or the login returned no evidence either way. `unknown` is not a failure and does not block a run — the pentest agent drives the full login flow at scan time — but it does mean this check cannot confirm the credential for you. Allowlisting the Nullify source IPs (below) removes the most common cause.

## 4. Allowlist the Nullify source IP

Pentest traffic originates from a small, static set of IPs specific to your tenant's region. Retrieve them with:

```
GET /dast/sourceips
```

They are also shown on the Pentest page in the dashboard.

Allowlist those addresses on any WAF, API gateway, or secure web gateway in front of the target. Skipping this step is the most common reason an engagement stalls.

Use the allowlisted source IPs to identify and filter pentest traffic. Request headers are not a stable traffic boundary: different pentest tools use different `User-Agent` values, and `X-Nullify-ID` is not added to every request. Do not build WAF rules or log filters that depend on either header.

## 5. Rate limits

The pentester backs off when it receives a 429, but aggressive rate limiting still reduces coverage. Relax or exempt limits for the Nullify source IPs during the test window.

## 6. Repository

The GitHub organisation and repository name for the service. This lets Nullify attach findings to the right repository, route them to code owners, and generate fix pull requests.

## 7. Test window and scope

Agree before the run:

* Start and end of the test window
* Whether destructive testing is acceptable, and whether test data is disposable
* Endpoints that are explicitly out of scope — anything moving money, exporting regulated data, or calling a third-party partner API
* An escalation contact if the environment degrades

You can stop a run at any time from the dashboard or with `POST /dast/pentest/scans/{scanId}/stop`.

## Preflight

Once the application, target, and credentials are in place, run a preflight check. It confirms the target is reachable, the credentials authenticate, and a specification is available. It also estimates cost and duration.

```
POST /dast/pentest/applications/{applicationId}/preflight
```

You get five preflight runs per application per day. Check remaining quota with `GET /dast/pentest/applications/{applicationId}/preflight/quota`.


# Exploit Validation

## Overview

Exploit Validation proves whether an individual finding is *actually* exploitable by attacking a running target — without launching a full [Pentest](/capabilities/pentests). When code review flags an injection point, or dependency analysis surfaces an exploitable CVE, Nullify can drive a focused exploitation agent against the deployed application, read the vulnerable source, reach the code path over real HTTP, and record the result as evidence.

Where a Pentest goes *broad* — mapping an application and hunting for anything exploitable — Exploit Validation goes *narrow*: one already-known finding, one verdict, backed by proof. It turns a theoretical "this looks reachable" into a confirmed "this was reproduced" (or a defensible "this could not be exploited").

Validation runs against the Web Services and Targets you register in your inventory, and it only runs when a **policy** you control says it should.

{% hint style="info" %}
Exploit Validation reuses the same evidence-first exploitation engine as Pentests, scoped to a single finding. A validated finding carries the same reproduction evidence you would get from a full engagement.
{% endhint %}

## How it works

1. **A finding becomes a candidate.** During [Triage](/capabilities/triage), a code-review (SAST) or dependency (SCA) finding is assessed as reachable and likely exploitable.
2. **Policies decide whether to run.** Nullify checks your Exploit Validation [policies](#policies). If an enabled policy matches the finding — by finding source and validation window — and the finding isn't inside its revalidation cooldown, validation is started.
3. **Targets are resolved.** The finding's repository is matched to the Web Services named in the policy, and Nullify resolves the live target URLs to attack.
4. **The agent attempts the exploit.** A focused agent reads the vulnerable code, discovers the reaching endpoint, and sends bounded, real HTTP requests — using out-of-band callbacks where blind exploitation requires it. Credentials are used for authenticated access and are redacted from all logs and traces.
5. **An outcome is recorded.** The agent returns a verdict with proof. The result is written back onto the finding's triage — so a confirmed exploit moves the finding's exploitability verdict and priority — and appears as an **attempt** on the Exploit Validation page.

The **Exploit Validation** page holds both halves: the policies that govern validation, and the attempts that have run.

<figure><img src="/files/IMMUMaYUI1TnA3YyIZeM" alt="The Exploit Validation page, showing validation policies above the table of validation attempts"><figcaption><p>Validation policies and the attempts they produced</p></figcaption></figure>

## Outcomes

Every validation run is recorded as an **attempt**. While a run is underway the attempt shows as **In progress**; when it finishes it lands on one of these outcomes:

| Outcome              | Meaning                                                                                                                                                                                                             |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Confirmed**        | Nullify proved exploitability with a concrete HTTP response or an out-of-band callback. The finding is exploitable.                                                                                                 |
| **Not Exploitable**  | The correct reachable code path was exercised with the context it needed and the exploit still did not work — a strong negative signal.                                                                             |
| **Cannot Determine** | Validation was blocked before a verdict could be reached (for example the target was unreachable, access was blocked, authentication was required, a rate limit or request budget was hit, or context was missing). |

A **Cannot Determine** result is not a pass or a fail — it means the test setup, not the application, needs attention. The recorded reason (unreachable target, blocked access, auth required, budget exhausted, and so on) tells you what to fix so a re-run can reach a real verdict.

The attempts table lists each attempt's finding, source, outcome, confidence, any exploited CVE, and when it was validated — newest first.

## Evidence

Every validated finding carries its exploit-validation report inline, on the finding itself: the outcome, Nullify's confidence, how long the run took, the endpoints it tested, a runnable **reproduction script**, and a written report covering the vulnerability, how it was exploited, the impact, and the fix — followed by the proof-of-exploit requests themselves.

<figure><img src="/files/mxOM1s3xl3X71Rmshh8w" alt="A confirmed exploit-validation report on a finding, with tested endpoints, a reproduction script, and proof of exploit"><figcaption><p>A confirmed validation on a finding: tested endpoints, reproduction script, and proof of exploit</p></figcaption></figure>

Because the reproduction script is a plain shell script against your own environment, you can re-run Nullify's proof yourself — and re-run it after a fix to confirm the exploit no longer works.

## Policies

Policies are how you control **whether, when, and against what** Exploit Validation runs. Validation attempts are live LLM-driven exploitation runs, so nothing fires until a policy you have configured matches a finding. A finding is validated only when at least one **enabled** policy matches it.

Each policy defines:

| Setting                   | What it controls                                                                                                                                                                                                                |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Name**                  | A label for the policy.                                                                                                                                                                                                         |
| **Policy enabled**        | Turns the policy on or off. Disabled policies never start exploit validation.                                                                                                                                                   |
| **Finding sources**       | Which sources the policy applies to — **SAST** (source-code vulnerabilities), **SCA Dependencies** (vulnerable open-source dependencies), or both. At least one is required.                                                    |
| **Web services**          | Which registered Web Services validation may run against. At least one is required — this is how a finding's repository is mapped to a live target to attack. Pick existing services from your inventory, or create one inline. |
| **Validation window**     | The days and local-time window in which new validations may *begin* — for example weekdays 09:00–17:00 in `Australia/Sydney`, or "all day" on the days you select.                                                              |
| **Revalidation cooldown** | How many days before the same finding can be validated again (default 30, minimum 7). Prevents the same finding from being re-attacked on every scan.                                                                           |

<figure><img src="/files/PKa6Abo3kC7XMUz0zMgY" alt="The Add Exploit Validation Policy dialog, showing name, enabled toggle, finding sources, web services, validation window and revalidation cooldown"><figcaption><p>Creating an exploit-validation policy</p></figcaption></figure>

Two details worth knowing about the validation window: it gates when a validation may **start**, so a run already underway is allowed to finish rather than being cut off at the boundary; and an overnight window (one whose end time is earlier than its start) belongs to the day it *starts* on.

Findings are also held to a severity and exploitability floor, so low-severity or unlikely-to-be-exploitable findings aren't attacked. By default a finding must be **High** severity or above and assessed as **Likely** exploitable by [Triage](/capabilities/triage); where the controls are available you can tune both floors per policy.

When a finding is proposed for validation, Nullify keeps only the policies that are enabled and match on finding source, validation window, and those floors. If any match, it then checks the finding's most recent attempt against the cooldown, and resolves the matching web services to target URLs. If nothing matches — no enabled policy applies, the finding is inside its cooldown, or no matching policy has a web service for the finding's repository — validation is skipped and the reason is recorded.

## How validation feeds triage

Exploit Validation closes the loop with [Triage](/capabilities/triage): a confirmed exploit is strong evidence that a finding is real and reachable, so it raises the finding's exploitability verdict and its priority. A **Not Exploitable** result biases the finding the other way. The validation outcome is folded into the finding's triage as evidence the model weighs — not a blunt override — so ranking still reflects the full picture (reachability, business context, and confidence) rather than the exploit result alone.

## Where to find it

Everything lives on the **Exploit Validation** page in the web console, under **Assessments**. From there you can:

* Create, edit, enable/disable, and delete **validation policies**.
* Review every validation **attempt**, newest first, with its outcome and confidence.
* Jump from an attempt straight to the finding it validated.

Each validated finding also shows its full exploit-validation report inline, in the finding's own detail view.

## Running validation via the API

Exploit Validation is available through the [Nullify API](/api-reference/api-reference) under the `/dast/exploit-validation` namespace — list attempts, manage policies, and request validation for a specific finding.

## Exploit Validation vs Pentests

| Aspect       | Exploit Validation                                   | Pentests                                                            |
| ------------ | ---------------------------------------------------- | ------------------------------------------------------------------- |
| **Scope**    | One known finding (from code review or dependencies) | A whole application                                                 |
| **Goal**     | Prove or disprove a single finding's exploitability  | Discover and exploit anything reachable                             |
| **Trigger**  | Policy-driven, off the back of triage                | On-demand                                                           |
| **Depth**    | A focused attack on one code path                    | Endpoint mapping, fuzzing, and multi-stage exploit chains           |
| **Evidence** | Reproduction proof for one finding                   | Request/response pairs and reproduced exploit chains across the app |


# Supported Targets

## Overview

The pentest workflow targets REST and GraphQL APIs. The [Bug Hunt](/capabilities/bug-hunts) workflow assesses server-side rendered applications and single-page applications instead.

## Application Types

| Type                         | Supported            | Notes                                                                                                                                                           |
| ---------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **REST APIs**                | Yes                  | Full support. Deepest results with an OpenAPI or Swagger specification — see [Preparing for an Engagement](/capabilities/pentests/preparing-for-an-engagement). |
| **GraphQL APIs**             | Yes                  | Introspection, mutation testing, and authorization-matrix analysis.                                                                                             |
| **SSR HTML Apps**            | Bug Hunt only / beta | Server-side rendered apps (Django, Rails, Express) are assessed by the [Bug Hunt](/capabilities/bug-hunts) workflow, not the pentest workflow.                  |
| **SPAs**                     | Bug Hunt only / beta | Single-page apps (React, Vue, Angular) are assessed by the [Bug Hunt](/capabilities/bug-hunts) workflow, not the pentest workflow.                              |
| **SOAP APIs**                | Not yet              | Register interest with the Nullify team.                                                                                                                        |
| **WebSockets**               | Not yet              | Event-driven assessments are on the roadmap.                                                                                                                    |
| **gRPC**                     | Not yet              | Reach out if you would like to join the design partner programme.                                                                                               |
| **Custom TCP/UDP Protocols** | Not yet              | On the roadmap.                                                                                                                                                 |

## APIs that carry XML

The pentester classifies a target as either REST or GraphQL. It can test an HTTP API that exchanges XML payloads as REST, provided an OpenAPI specification covers its endpoints.

A SOAP service is different. We do not support SOAP's envelope, WSDL contract, or operation dispatch, and a WSDL cannot stand in for an OpenAPI specification. If you are unsure which category your service falls into, send us the specification and we will confirm before the engagement starts.

## Network reachability

| Target location             | Pentest                                                                                                          | [Exploit validation](/capabilities/pentests/exploit-validation) |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| Reachable from the internet | Yes                                                                                                              | Yes                                                             |
| Private network             | Yes — via the CLI or AWS PrivateLink, see [Running in Private Networks](/capabilities/pentests/private-networks) | No                                                              |

## Vulnerability coverage

Across supported target types the pentester tests for:

* Injection — SQL, NoSQL, command, LDAP, and XPath
* Broken authentication and broken authorization
* Insecure direct object references and broken object-level authorization
* Privilege escalation and authorization bypass across roles
* Server-side request forgery, including reachability of cloud metadata endpoints
* XML external entity processing
* Unsafe file upload
* Business logic flaws

Where an exploit succeeds, the pentester chains steps to show real impact — endpoint discovery, parameter fuzzing, injection, lateral access, and data exfiltration — and attaches the requests and responses that prove each step.


# Private Networks

## Overview

Need to keep pentester activity inside your own network boundary? Run the engagement from within your own environment using the Nullify CLI. The CLI executes the DAST agent locally — pointed at internal hosts that only your network can reach — and streams results back to your hosted tenant, so the traffic never leaves your boundary.

> This applies to pentests only. [Exploit validation](/capabilities/pentests/exploit-validation) requires a target reachable from the internet and has no private-network equivalent.

## Orchestration Options

* **CLI** — The [Nullify CLI](https://github.com/Nullify-Platform/cli) launches pentests from inside your network and reports results to the platform. Pass the **`--local`** flag to `nullify pentest` to run the scan locally: the CLI executes the agent inside the Nullify DAST image (`ghcr.io/nullify-platform/dast`) it pulls on first use, pointed at internal hosts only your network can reach. Use `--image-label` to pin a specific tag, `--force-pull` to always pull the latest, and `--use-host-network` when the target is only reachable on the host network.
* **Continuous integration** — Run the same `--local` command on a self-hosted runner that sits inside your network, so private engagements execute as part of a release workflow. See [Engagement Workflow](/capabilities/pentests/engagement-workflow) for authentication in CI.

Contact support for IAM policies and sizing recommendations tailored to your environment.

## AWS PrivateLink

For customers who need pentest traffic to stay on a private network, Nullify supports connectivity via [AWS PrivateLink](https://docs.aws.amazon.com/vpc/latest/privatelink/what-is-privatelink.html). Instead of routing traffic over the public internet, Nullify connects to your applications through a VPC endpoint, keeping all communication within the AWS backbone.

### Prerequisites

* A VPC Endpoint Service in your AWS account. AWS PrivateLink supports endpoint services backed by a Network Load Balancer (NLB), Gateway Load Balancer (GWLB), or directly by an application running on supported compute (e.g. ECS, EKS, EC2).
* The endpoint service must be in an **Available** state.
* Your target applications accessible on the required ports from within the VPC.

### Setup Steps

#### 1. Create a VPC Endpoint Service

If you haven't already, create an endpoint service in the AWS Console or CLI that routes to your pentest target applications. See the [AWS documentation](https://docs.aws.amazon.com/vpc/latest/privatelink/create-endpoint-service.html) for supported configurations.

#### 2. Share the service details with Nullify

Provide the Nullify team with:

* Your **VPC Endpoint Service name** (e.g. `com.amazonaws.vpce.<region>.vpce-svc-<id>`).
* The **region** the service is deployed in.
* The **ports** your applications listen on.

#### 3. Whitelist the Nullify AWS account

Add the Nullify account as an allowed principal so we can create an interface endpoint to your service. The Nullify team will provide the account ID.

```bash
aws ec2 modify-vpc-endpoint-service-permissions \
  --service-id <your-service-id> \
  --add-allowed-principals arn:aws:iam::<nullify-account-id>:root \
  --region <region>
```

#### 4. Accept the VPC endpoint connection

Once Nullify creates the interface endpoint, accept the pending connection request. You can do this in the AWS Console under **VPC → Endpoint Services → Endpoint Connections**, or via the CLI:

```bash
aws ec2 accept-vpc-endpoint-connections \
  --service-id <your-service-id> \
  --vpc-endpoint-ids <endpoint-id> \
  --region <region>
```

#### 5. Update security groups

Ensure the security group attached to your target applications allows inbound traffic from the Nullify VPC CIDR range on the required ports. The Nullify team will provide the CIDR range to whitelist.

> **Note:** A self-referencing security group rule (one that only allows traffic from itself) will not permit traffic arriving through the VPC endpoint interface. You must add an explicit inbound rule for the Nullify CIDR range.

#### 6. Verify connectivity

The Nullify team will confirm connectivity to your applications over the private link. Once verified, the pentest can begin.

### Troubleshooting

* **InvalidServiceName error** — Confirm the endpoint service region matches and that the Nullify account has been added to the allowed principals.
* **Connection timeouts** — Ensure your security group allows inbound traffic from the Nullify VPC CIDR range on the required ports.
* **Endpoint state not Available** — Verify the endpoint service is active and the underlying Network Load Balancer targets are healthy.

> For assistance with PrivateLink setup, contact the Nullify team at <support@nullify.ai>.


# Triage

## Overview

Finding a potential vulnerability is the easy part — deciding whether it's real, how urgent it is, and who should deal with it is where most security programs drown. Triage is what Nullify does between detection and remediation, and it's applied to **every** finding, from every capability: code review, dependency and container analysis, secrets detection, cloud audits, supply-chain posture, bug hunts, and pentests.

The goal is simple: the things in front of you should be the things that actually matter, ranked in the order you should act on them.

## What triage evaluates

For each finding, Nullify weighs:

* **Reachability** — is the vulnerable code or dependency actually reached by your application, and is the asset reachable from the internet? A vulnerable package your code never calls, or a misconfiguration on an internal-only asset, is not the same risk as one on an internet-facing path.
* **Exploitability** — can this actually be exploited, and has Nullify proven it? A pentest finding backed by a reproduced exploit outranks a theoretical one; a leaked credential that's still live outranks one that's already been rotated. Nullify can also prove a single code-review or dependency finding on demand — see [Exploit Validation](/capabilities/pentests/exploit-validation).
* **Business context** — what does the affected service do, what data flows through it, and how critical is it? Nullify draws this from the [Context Engine](/capabilities/context-engine) and the organizational context you provide.
* **Confidence** — how sure is Nullify that this is a true positive? Low-confidence pattern matches are screened out rather than dumped on you.

Triage combines Nullify's reasoning with deterministic rules, so the same finding lands in the same place every time.

## Severity and priority

Nullify tracks two related but distinct signals:

* **Severity** — how bad the issue is in the abstract (roughly, how much damage it could do).
* **Priority** — how urgently *you* should act on it, once reachability, exploitability, and business context are folded in. Priority is the ranking that drives ticketing thresholds, SLAs, and the order work is surfaced. Nullify's priority labels run from `URGENT` and `IMPORTANT` down through `MEDIUM`, `LOW`, `INFORMATIONAL`, and `NEGLIGIBLE`.

A critical-severity CVE in a dependency your code never loads can carry a lower priority than a high-severity issue on an internet-facing service — because priority reflects real-world risk, not just the raw score.

## False-positive screening

Before a finding ever reaches you, Nullify screens out the noise: unreachable code paths, test fixtures and example credentials, benign configuration variation, and duplicate reports of the same underlying issue. What survives is meant to be worth a human's attention.

## Re-triage

Triage isn't a one-time verdict. When something changes — new code lands, a dependency is upgraded, a fix merges, or new threat intelligence arrives — Nullify re-triages the affected findings and updates their priority. A finding that becomes reachable is escalated; one that's been fixed or is no longer exploitable is de-prioritized or resolved.

## Your decisions stick

When you make a triage decision — allowlist a finding, accept a risk, mark a false positive, or override a severity — Nullify remembers it. Those decisions persist across future scans and feed back into how Nullify triages similar findings, so you don't re-litigate the same call every time a scan runs. See the per-capability configuration pages for how to allowlist and adjust findings.

## Where triaged findings go

Triaged findings flow into [Security Program Management](/capabilities/program-management), which sequences and assigns the work, and into [Remediations](/capabilities/remediations), which drafts the fixes. When Nullify needs a human judgment call it can't make alone — an escalation decision, a clarifying question, an ownership mapping — it raises an [action item](/capabilities/working-with-nullify).


# Security Program Management

## Overview

Detecting and fixing vulnerabilities is only useful if the right work happens in the right order, with the right people, at the right time. Security Program Management is the part of Nullify that runs your security program the way a security engineering lead would: it maintains a prioritized backlog across every capability, monitors emerging threats, understands who owns what and how much they have on their plate, and drives work to completion through campaigns — opening PRs, filing tickets, nudging, and escalating.

## Campaigns

A **campaign** is how Nullify organizes a body of security work toward an objective — "eliminate leaked secrets," "upgrade the dependencies affected by this CVE," "harden our Terraform." Campaigns are the unit you see and steer in the dashboard and API.

### How a campaign runs

1. **Detect** — Nullify proposes campaigns from your current backlog, threat intelligence, and organizational risk, tailored to what it actually found. There's no fixed catalog of templates to pick from.
2. **Decide** — a security leader adjusts the scope and the level of automation: which findings are in, whether Nullify opens PRs, files tickets, or just notifies, and any deadlines.
3. **Execute** — Nullify assigns work to the right owners, opens fix PRs, creates and updates tickets, and escalates when progress stalls.
4. **Report** — progress is tracked in real time: findings in scope, resolved vs. remaining, time to resolution, per-team progress, and SLA compliance.

### Scoping a campaign

Campaigns can target work by finding type (specific CWEs or CVEs), repository, team, individual, severity, or a combination. Campaign targeting currently covers code, dependency, container, secret, and pentest findings.

Common campaign themes Nullify generates include eliminating credential leaks, upgrading vulnerable dependencies, hardening Terraform, closing OWASP Top 10 gaps, and meeting compliance requirements (PCI-DSS, HIPAA, SOC 2) — but every campaign is generated to fit what Nullify finds, not chosen from a fixed list.

## The Backlog

Nullify maintains a single curated backlog that combines findings from across the platform — code vulnerabilities and leaked secrets from [code review](/capabilities/code-reviews), infrastructure misconfigurations from [cloud audits](/capabilities/cloud-audits), supply-chain risks from [SCPM](/capabilities/scpm), and validated findings from [pentests](/capabilities/pentests) and [bug hunts](/capabilities/bug-hunts).

The backlog is ordered by an overall **priority score** (0–100) that weighs severity, exploitability, reachability, and business impact together — not raw CVSS alone. Resolved findings drop off automatically, and each remaining finding is linked to the PRs and tickets tracking its remediation so status stays current. See [Triage](/capabilities/triage) for how findings are scored.

## Threat Intelligence

Nullify continuously researches the external threat landscape for risks specific to your stack — actively exploited vulnerabilities and zero-day disclosures affecting the technologies and dependencies you actually run. This intelligence feeds prioritization and escalation: a finding that becomes actively exploited is pushed up, and a serious enough disclosure can trigger a campaign.

When a major threat drops, Nullify opens a **threat investigation** — a trackable record of the threat, which of your repositories are affected, and the remediation progress against it. You can list, view, and act on investigations from the dashboard and API.

## Ownership and Capacity

Good routing depends on knowing who owns what and who's overloaded.

* **Ownership** comes from CODEOWNERS, commit history, service metadata, and connected sources like [Compass](/connectors/compass) and your team structure.
* **Capacity** is a lighter-weight, qualitative read: Nullify considers how many open PRs and assigned tickets a person already has, and their recent history of merging and closing work, so it can route new work to people who can actually take it on and avoid piling onto someone who's swamped.

Nullify respects team boundaries — it won't reassign work across teams without approval. When it can't determine ownership, it raises an [action item](/capabilities/working-with-nullify) asking you to assign a team.

## Assigning and driving work

When Nullify assigns and sequences work, it weighs the finding's priority (severity, exploitability, reachability, business impact), current ownership, and team capacity. It then drives each item to completion:

* Opens [autofix](/capabilities/remediations) pull requests and assigns reviewers using the ownership cascade described in Remediations.
* Creates and updates tickets in Jira and other trackers.
* Monitors open security PRs, comments with context, and escalates the ones that stall.
* Tracks each finding's status as its PR or ticket moves.

## Security Program Strategy

Beyond day-to-day work, Nullify maintains a rolling **strategy** — a set of quarterly security goals for your program. The strategy is versioned (so you can see how it's evolved), editable, and Nullify can generate a proposed strategy from your current posture and risk profile. It gives the program a direction that individual campaigns ladder up to.

## Ground Rules

You set the boundaries Nullify operates within. **Ground rules** are per-action-type controls — you decide which autonomous actions Nullify may take on its own (for example, whether it may open fix PRs, triage automatically, or select reviewers) and which require a human. These are configured under **Configure → Manager** in the dashboard. Nullify operates inside those rules everywhere it acts.

## Metrics & Reporting

Track the program's effectiveness with:

* **Mean Time to Remediate (MTTR)** — average time from discovery to fix, plus related mean-time variants.
* **Finding counts** — backlog totals and trend over time.
* **Team velocity** — fixes per sprint or week.
* **SLA compliance** — the share of findings fixed within their SLA.
* **Campaign progress** — resolved vs. remaining, per team, with burn-down.

## How it connects

Security Program Management sits at the center of Nullify: it reads triaged findings and the [Context Engine](/capabilities/context-engine), drives [Remediations](/capabilities/remediations), routes through your [ticketing](/connectors/jira) and [messaging](/connectors/slack) connectors, and raises [action items](/capabilities/working-with-nullify) when it needs a human decision.


# Remediations

## Overview

Nullify doesn't just find and rank vulnerabilities — it fixes them. For code vulnerabilities, dependency issues, leaked secrets, cloud misconfigurations, and validated pentest findings, Nullify drafts production-ready fixes, opens pull requests that match your codebase's style, and shepherds each one until it's ready for a developer to merge. The bar it holds itself to: the vulnerability is genuinely closed, existing functionality is preserved, and no new vulnerability is introduced.

## How a fix is produced

Only findings that have been [triaged](/capabilities/triage) as real and worth fixing become fix candidates — Nullify doesn't open PRs for noise. From there, it works like a careful engineer rather than a find-and-replace bot:

1. **Locate and plan.** Nullify finds the code responsible and writes a minimal-change plan that targets the root cause, not just the symptom.
2. **Edit.** It applies the change, matching your surrounding error handling, logging, and style conventions.
3. **Independent review.** A separate reviewing step checks that the fix actually addresses the root cause, preserves behavior, and stays minimal. If it doesn't, the fix loops back for revision.
4. **Prove it.** Before any PR opens, the fix is verified — the original scanner is re-run to confirm the finding is gone, and the change is exercised (build, tests, and, where a runnable exploit exists, re-running that exploit to confirm it no longer works while the feature's own tests still pass). A fix that closes the vulnerability but breaks functionality is rejected.

Only after surviving that does Nullify open a pull request — as a **draft** first.

## The pull request lifecycle

Opening the PR is the start of Nullify's work, not the end. It keeps iterating the way a diligent author would:

* **Reads CI and fixes it.** When CI fails, Nullify reads the actual failure logs and pushes corrective commits, within a bounded effort budget.
* **Responds to reviewers.** When a developer leaves a review comment, Nullify reads the diff and the discussion and responds inline — updating the code or explaining the change.
* **Handles conflicts.** If the branch falls behind, Nullify rebases it.
* **Promotes when green.** Once CI is passing, Nullify promotes the PR from draft to ready-for-review and assigns the right reviewer.

Nullify never merges your code — a human always makes the final call. Its job is to hand you a PR that's already green, already reviewed against its own bar, and already in front of the right person.

### Ask Nullify on the PR

Reply in the PR thread — or **`@mention`** Nullify — to ask why a change is needed, request a different approach, or get more context. Nullify reads the diff and the discussion and responds inline, which is the fastest way to get a fix review-ready and merged. On pull requests Nullify didn't open, `@mention` it so it knows the comment is for it.

## Choosing the reviewer

Nullify assigns each fix PR to the person most likely to be able to review it well, using a ranked ownership cascade:

1. **The authors of the changed code** — the people whose recent commits touched the lines being modified (automated/bot accounts excluded).
2. **Code owners** — the owners defined in your CODEOWNERS file for the affected paths.
3. **The owning team** — falling back to the team that owns the repository or finding.

Reviewer assignment is capped to a sensible number of people so a PR isn't spread across half the org.

## Pull Request Limits

To avoid overwhelming developers, autofix applies fixed, platform-managed limits rather than per-repository configuration:

* **3 open Nullify fix PRs per repository** at any time.
* **5 new fix PRs per remediation plan.**

Once a fix PR merges or closes, the next queued fix can open. These limits are built-in platform behaviour and are not configurable per repository.

## Working with developers

### Status Updates

* Posts to existing Slack threads.
* Updates Jira tickets with PR links.
* Sends notifications when a review is needed.
* Escalates stalled PRs to team leads.

### Manual Fallback

When an automated fix isn't appropriate, Nullify provides detailed fix guidance, creates a ticket with reproduction steps, tags the appropriate owner, and preserves the context for future learning.

## Where remediation fits

Remediation is driven by [Security Program Management](/capabilities/program-management), which decides what to fix and when, and reads from the [Context Engine](/capabilities/context-engine) for ownership and business context. When a fix needs a human decision Nullify can't make alone, it raises an [action item](/capabilities/working-with-nullify).


# Working with Nullify

## Overview

Nullify runs autonomously, but the best security programs are a partnership. There are moments where Nullify needs a human decision — a judgment call it shouldn't make alone, a piece of context only your team has, or an approval before it acts. This page covers how that back-and-forth works: the **action items** Nullify raises, the **chat** you can hold with it anywhere, and the **escalations** it sends when something needs attention.

## Action Items

An **action item** is a tracked unit of work that Nullify files when it genuinely cannot proceed on its own. Rather than guessing or stalling silently, it asks — and once you answer, it picks the work back up automatically. There are a few kinds:

* **Escalated finding** — Nullify wants your call on a finding before acting: approve it, reject it, or ask for changes.
* **Clarifying question** — Nullify is missing a piece of context (a policy, an environment detail, an ownership rule). Answer once, and it re-triages every finding that was waiting on that answer.
* **Unmapped user** — Nullify knows the person involved in one system (say, a GitHub handle) but needs help mapping them to another (say, their Slack account) so it can route work to them.
* **Unassigned team** — a repository or finding has no owning team. Assign one, and every unassigned finding in that scope is routed.

You resolve an action item simply by supplying the judgment it asked for — from the dashboard, or by replying in the Slack thread or ticket comment where it reached you. Nullify only marks an action item complete once the underlying work has actually happened, not because it *said* it would; and it never auto-closes one just because time passed. If the underlying problem goes away on its own, Nullify resolves the item for you.

You also have a few deliberate controls of your own: reopen a finding Nullify dismissed (with a reason), re-route ownership, or just answer in chat.

## Chat with Nullify

You can talk to Nullify wherever you already work — the dashboard, [Slack](/connectors/slack), [Microsoft Teams](/connectors/microsoft-teams), and in pull request and ticket threads. Ask it about:

* **Findings** across code, dependencies, containers, secrets, cloud posture, and pentests.
* **Remediation campaigns** and their progress.
* **Security metrics and trends.**
* **Threat investigations** and which repositories are affected.

Nullify answers in plain language and links back to the relevant place in the dashboard. From the dashboard chat it can also **act** on your behalf — reassign a pull request's reviewers, reassign, comment on, or close a ticket, rebase or promote a stalled fix PR, allowlist a finding, or create a ticket. In Slack and Teams, chat is oriented toward questions and answering Nullify's action items; for state-changing actions it points you to the dashboard.

## Escalations

When a finding needs developer action and isn't moving — a fix PR is open but unreviewed, a fix is blocked, or a secret needs rotating — Nullify sends an **escalation**. Each one carries the finding's facts, why it needs attention, the risk and SLA, and a link to the finding, delivered as a direct message to the owner, to a channel, or both. Reply in the thread to discuss, answer, or ask for more context.

See [Slack notifications & escalations](/connectors/slack/slack-notifications) for how to configure where escalations land. Escalation paths and the autonomous actions Nullify is allowed to take are part of [Security Program Management](/capabilities/program-management).


# Overview

## Overview

Nullify meets teams inside their existing tooling.

> Nullify's preferred operating mode is asynchronous: once connectors are configured, findings, fixes, and campaign updates flow to teams automatically, without requiring them to live inside the Nullify UI.

The following connectors are available today:

* **Version control** — [GitHub](/connectors/github), [GitLab](/connectors/gitlab), [Bitbucket Cloud](/connectors/bitbucket), and [Azure DevOps](/connectors/azure) (installation guides in the Enterprise Tier section).
* **Ticketing** — [Jira Cloud](/connectors/jira) with two-way sync for ownership, status, priorities, and manual issue creation (default issue type **Task**), and [Linear](/connectors/linear).
* **Messaging** — [Slack](/connectors/slack) and [Microsoft Teams](/connectors/microsoft-teams) for proactive coaching, escalations, and campaign updates.
* **Developer metadata** — [Atlassian Compass](/connectors/compass) to import team ownership and component mappings.
* **Identity** — [OIDC](/connectors/oidc) single sign-on for authenticating users into Nullify.
* **Cloud** — [AWS](/connectors/aws), [GCP](/connectors/gcp), and [Kubernetes](/connectors/kubernetes) connectors for infrastructure context and exposure analysis.
* **CI/CD** — [Buildkite](/connectors/buildkite), [Jenkins](/connectors/jenkins), and [CircleCI](/connectors/circleci) for build log ingestion, enabling autofix iteration on failing CI pipelines.


# GitHub

## Overview

Install the Nullify GitHub App to connect Nullify to your repositories. Nullify works with GitHub.com and GitHub Enterprise. Installation instructions differ for cloud and self-hosted deployments — see the [Enterprise Tier](/enterprise-tier/github-install) section for step-by-step guides.

Nullify posts updates asynchronously so developers stay inside their existing workflow.

## What Nullify does with GitHub

* **Pull request reviews.** When a pull request is opened, reopened, or updated, Nullify reviews the changed code and posts its findings back to the pull request as review comments and a **Nullify** check. When a PR is merged or closed, Nullify reconciles the state of any of its own autofix pull requests.
* **Push scanning.** Pushes are scanned, and pushes to the default branch refresh the repository's baseline so dashboards and finding counts stay current.
* **Autofix pull requests.** Nullify opens fix PRs for eligible findings, keeps them green through CI, responds to review comments, and assigns reviewers. See [Remediations](/capabilities/remediations).
* **Comment-driven discussion.** Replies on a Nullify pull request — or an `@mention` of Nullify on any PR — are routed back so it can respond in-line.

## Events handled

Nullify subscribes to the events it needs to review and remediate — including push, pull-request, pull-request-review and review-comment, issue-comment, check-run and check-suite, status, repository, and the app installation lifecycle. Settings such as the CI/PR gate, ticketing thresholds, and notification destinations are configured in the Nullify dashboard under **Configure**, not in a file in your repository.

## Support

If you encounter any issues, please contact our support team for assistance.


# GitLab

## Overview

Connect Nullify to your GitLab instance to assess your projects, review merge requests, and scan every push. Nullify works with GitLab.com (SaaS) and self-managed GitLab. Installation steps are documented in the Enterprise Tier section.

Nullify posts updates asynchronously so developers stay inside their existing workflow.

## What Nullify does with GitLab

* **Merge request reviews.** When a merge request is opened, reopened, or updated, Nullify scans the changed code and posts its findings back to the merge request. When a merge request is merged or closed, Nullify reconciles the state of any of its own autofix merge requests and records the outcome.
* **Push scanning.** Every push to a branch triggers a scan. Pushes to the project's default branch additionally update the repository's baseline so dashboards and finding counts stay current. Pushes that introduce no new commits are ignored.
* **Comment-driven discussion.** Replies on a Nullify merge request thread are routed back to Nullify so it can respond in-line, keeping the review conversation in GitLab.
* **Pipeline awareness.** Nullify tracks the result of pipelines associated with a merge request, mapping success/failure back to the corresponding scan so the CI/PR gate can act on it.

## How it connects

Nullify connects to GitLab through two mechanisms set up during install (see the Enterprise Tier guide):

1. **An access token** that lets Nullify call the GitLab API — resolving the group that owns a project, posting merge-request reviews, and reading diffs. The token is stored encrypted by the Nullify platform.
2. **A project (or group) webhook** that delivers events to Nullify's ingestion endpoint at `/core/gitlab/webhook`. Each delivery carries a secret token in the `X-Gitlab-Token` header, which Nullify verifies before processing the event.

## Events handled

Nullify subscribes to the following GitLab webhook events:

| GitLab event           | Used for                                                                       |
| ---------------------- | ------------------------------------------------------------------------------ |
| Push events            | Branch push scanning; default-branch pushes refresh the baseline               |
| Merge request events   | Merge request scans on open/reopen/update; state reconciliation on merge/close |
| Comments (note events) | Routing replies on Nullify merge-request threads                               |
| Pipeline events        | Mapping merge-request pipeline results back to scans                           |

Pushes that introduce no new commits are ignored. Nullify does not currently act on tag-push, deployment, issue, or job events.

{% hint style="info" %}
Settings such as the CI/PR gate, ticketing thresholds, and notification targets are configured in the Nullify dashboard under **Configure**, not in a file in your repository.
{% endhint %}

## Support

If you encounter any issues, please contact our support team for assistance.


# Bitbucket

## Overview

Connect the Nullify Bitbucket app to review pull requests, scan pushes, and publish [repository dashboards](/connectors/bitbucket/repository-dashboards). Installation steps are documented in the [Enterprise Tier](/enterprise-tier/bitbucket-install) section.

Nullify posts updates asynchronously so developers stay inside their existing workflow.

## What Nullify does with Bitbucket

* **Pull request reviews.** When a pull request is created, updated, or commented on, Nullify reviews the changed code and posts its findings back to the pull request.
* **Push scanning.** Pushes are scanned, and pushes to the default branch refresh the repository's baseline.
* **Autofix pull requests.** Nullify opens fix PRs for eligible findings and iterates on them. See [Remediations](/capabilities/remediations).
* **Comment-driven discussion.** Replies on a Nullify pull request are routed back so it can respond in-line.

## Events handled

Nullify subscribes to repository push and pull-request events (created, updated, and comment-created). Settings such as the CI/PR gate, ticketing thresholds, and notification destinations are configured in the Nullify dashboard under **Configure**, not in a file in your repository.


# Repository Dashboards

Bitbucket repositories receive the same continuously updated dashboard as GitHub projects. It tracks outstanding findings, accepted risks, campaign assignments, and linked Jira tickets so teams have a single thread of truth.

![Repository dashboard example](/files/6Fwrg9Jr3sbKMSyNtaw1)


# Azure DevOps

## Overview

The Azure DevOps connector links your Azure DevOps organization to Nullify so that source-control activity drives security scanning, pull request reviews, and two-way ticket status sync — without developers leaving their existing workflow.

Nullify authenticates to Azure DevOps with the OAuth client-credentials flow (Client ID, Client Secret, and Tenant ID), then subscribes to Azure DevOps **service hooks** that deliver Git and work-item events to Nullify over an authenticated webhook.

{% hint style="info" %}
This page describes what the integration provides. For the step-by-step setup — registering the Azure app, granting permissions, and creating the webhooks — see the [Azure DevOps Install guide](/enterprise-tier/azure-install).
{% endhint %}

## What you get

### Pull request reviews

When a pull request is **created** or **updated** in a connected repository, Nullify reviews the changed code and posts its findings back as a PR review. Replies to Nullify's PR comments are also handled: when a developer comments on a Nullify review thread, the connector picks up the reply and continues the conversation in-thread.

Pull request gating and review behaviour are configured in the dashboard under **Configure → Pull Requests** (or via the admin API), including whether the "Nullify" check can fail the build on net-new findings at or above your chosen priority threshold.

{% hint style="info" %}
Nullify does not open autofix pull requests on Azure DevOps — autofix PR creation is currently available on other Git platforms. The Azure connector covers PR reviews, push scanning, and ticket sync.
{% endhint %}

### Push scanning

Every push to a connected repository is delivered to Nullify. The connector emits a scan event for each updated branch (tag pushes and branch deletions are ignored), and pushes to the repository's default branch additionally trigger the default-branch pipeline so your baseline security posture stays current.

### Work-item (ticketing) status sync

When Nullify files a finding as an Azure DevOps work item, **work-item updated** events flow back to Nullify. A state change on a tracked work item (for example moving it to *Resolved*, *Closed*, or *Done*) is synced to the matching Nullify finding ticket, keeping the finding's status aligned with what your team does in Azure Boards. Nullify maps Azure's out-of-the-box state vocabulary by default and honours a per-tenant status map for teams running a custom Azure Boards process.

## Events the connector subscribes to

Nullify creates Azure DevOps service-hook subscriptions for the following events on each connected repository:

| Event                  | Azure DevOps trigger                        | Used for                                      |
| ---------------------- | ------------------------------------------- | --------------------------------------------- |
| Code pushed            | `git.push`                                  | Push scanning (all branches + default branch) |
| Pull request created   | `git.pullrequest.created`                   | PR reviews                                    |
| Pull request updated   | `git.pullrequest.updated`                   | PR reviews                                    |
| Pull request commented | `ms.vss-code.git-pullrequest-comment-event` | PR review replies                             |
| Work item updated      | `workitem.updated`                          | Ticket status sync                            |

Webhook deliveries are authenticated: each subscription carries an HTTP Basic credential (username `nullify` plus a per-install shared secret), and Nullify rejects deliveries that don't match.

## Setup

Subscriptions can be created for you from the Nullify dashboard across the whole organization or per repository, or configured manually in Azure DevOps. The full walkthrough lives in the [Azure DevOps Install guide](/enterprise-tier/azure-install).


# Slack

## Overview

The Slack connector lets Nullify reach engineers where they already work. Once connected, Nullify can:

* **Escalate findings** that need developer action — an open fix PR that hasn't been reviewed, a fix that's blocked, or a secret that needs rotating.
* **Send campaign nudges and informational updates** to keep teams aligned on remediation.
* **Take replies in-thread**, so engineers can respond without leaving Slack.

Messages are delivered asynchronously, so teams stay productive inside Slack without hopping between tools.

## Ask Nullify directly

Engineers don't have to wait for a message — they can start a conversation with Nullify any time by **`@mention`-ing** it, sending it a **direct message**, or **replying in any thread** it's part of. Ask about:

* **Findings** across code, dependencies, containers, secrets, cloud posture, and pentests.
* **Remediation campaigns** and their progress.
* **Security metrics and trends.**
* **Threat investigations** and which repositories are affected.

Nullify replies in plain language and links to the relevant place in the dashboard. From a Slack thread you can also answer and resolve the [action items](/capabilities/working-with-nullify) Nullify raises — approve or dismiss an escalation, answer a question, or assign a team. For broader state changes like allowlisting a finding, triggering a fix, or creating a ticket, Nullify points you to the dashboard.

## Connect Slack

Connecting Slack is a one-time authorization — there's no app to build or token to paste.

1. Log in to the Nullify Dashboard.
2. Go to **Configure → Connectors → Slack**.
3. Click **Connect** and authorize the Nullify Slack app in your workspace via OAuth.

Once authorized, Nullify verifies the connection and marks the connector as **Enabled**. You're then ready to choose where escalations go — see [Configuring notifications & escalations](/connectors/slack/slack-notifications).


# Notifications & Escalations

Once Slack is connected, you can control where Nullify sends its messages — directly to the engineer who owns a finding, to a shared channel, or both.

## What an escalation is

An **escalation** is a high-priority nudge Nullify sends when a security finding needs developer action — for example, a fix PR is open but unreviewed, a fix is blocked, or a secret needs rotating.

Each escalation is a structured message containing:

* A greeting and the finding's facts (repository, and the file, package, CVE, or secret type).
* A status line describing why it needs attention.
* The risk and the SLA for resolving it.
* A link to the finding in the Nullify dashboard.

Engineers can **reply in-thread** to discuss, answer Nullify's questions, or ask it for more context.

Nullify's regular channel messages and campaign nudges are informational updates and follow the same channel configuration described below.

## Where to configure it

Go to **Configure → Connectors → Slack → Escalations** in the dashboard.

This is a single, **org-wide** policy — it applies to every escalation Nullify sends. If you don't configure anything, escalations default to **Direct message**, so existing behavior is preserved.

## Delivery modes

Choose one of:

* **Direct message** — DM the engineer who owns the finding. *(Default.)*
* **Channel** — post escalations to a Slack channel you choose.
* **Both** — DM the owner *and* post to the channel.

### Setting the channel

For **Channel** or **Both** modes, enter the destination **Slack channel ID** (for example, `C0123456789`). You can copy a channel's ID from Slack under the channel's **View channel details** menu.

The same channel is also used for the agent's regular channel messages.


# Microsoft Teams

## Overview

The Microsoft Teams connector lets Nullify reach engineers where they already work. Once the Nullify app is installed in your Teams tenant, Nullify can:

* **Escalate findings** that need developer action — an open fix PR that hasn't been reviewed, a fix that's blocked, or a secret that needs rotating.
* **Send campaign nudges and informational updates** to keep teams aligned on remediation.
* **Reply in-thread**, so engineers can respond and ask Nullify for more context without leaving Teams.

Messages are delivered asynchronously, so teams stay productive inside Teams without hopping between tools.

The Teams connector mirrors Slack: escalations, channel messages, and direct messages are all driven by Nullify, and the same notification configuration controls where they land.

## Ask Nullify directly

Engineers can start a conversation with Nullify at any time by **`@mention`-ing** it in a channel, sending it a **direct message**, or **replying in a thread** it's part of. Ask about:

* **Findings** across code, dependencies, containers, secrets, cloud posture, and pentests.
* **Remediation campaigns** and their progress.
* **Security metrics and trends.**
* **Threat investigations** and which repositories are affected.

Nullify replies in plain language and links to the relevant place in the dashboard. From a Teams thread you can also answer and resolve the [action items](/capabilities/working-with-nullify) Nullify raises — approve or dismiss an escalation, answer a question, or assign a team. For broader state changes like allowlisting a finding, triggering a fix, or creating a ticket, Nullify points you to the dashboard.

## Connect Microsoft Teams

Connecting Teams has two parts: registering an Azure AD application that Nullify authenticates as, and installing the Nullify app package into Teams.

1. **Register an Azure AD application** (your Azure administrator does this in the Azure portal): create an app registration, grant it the Microsoft Graph permissions Nullify needs to list channels and users and send messages, and create a client secret.
2. **Provide the credentials to Nullify.** In the Nullify Dashboard, go to **Configure → Connectors → Microsoft Teams** and enter the application's **Client ID**, **Client Secret**, and **Tenant ID**.
3. **Install the Nullify Teams app package.** From the same connector page, download the Nullify Teams app package (a `.zip` containing the app manifest and icons) and upload it in the Teams admin center (or via **Apps → Manage your apps → Upload an app**).
4. **Add the Nullify app to the team(s)** whose channels you want it to post in.

Once Nullify verifies the connection, the connector is marked **Enabled** and you're ready to choose where escalations go.

## Channel targeting

Nullify posts to a Teams channel you select from the dashboard — pick the channel and the destination is captured for you. Nullify only posts in teams the Nullify app has been added to. If a message doesn't appear, confirm the Nullify app is installed in that specific team, not just elsewhere in the tenant.

## Configuring notifications & escalations

Escalations and channel destinations are configured the same way as Slack, under **Configure → Connectors → Microsoft Teams**.

An **escalation** is a high-priority nudge Nullify sends when a security finding needs developer action — for example, a fix PR is open but unreviewed, a fix is blocked, or a secret needs rotating. Each escalation carries the finding's facts (repository, and the file, package, CVE, or secret type), why it needs attention, the risk and SLA, and a link to the finding in the Nullify dashboard. Engineers can **reply in-thread** to discuss or ask Nullify for more context.

Delivery modes match the Slack connector:

* **Direct message** — DM the engineer who owns the finding.
* **Channel** — post escalations to a Teams channel you choose.
* **Both** — DM the owner *and* post to the channel.

Nullify's regular channel messages and campaign nudges are informational updates that follow the same channel configuration.


# Jira

## Overview

Nullify can be configured to integrate with Jira to automatically create and update issues based on the findings in your project.

It can also be configured to automatically assign priorities to tickets based on the severity of the ticket, and assign tickets to users.

Nullify posts updates asynchronously so developers stay inside their existing workflows.


# Install

Nullify integrates with Jira as an [Atlassian Forge](https://developer.atlassian.com/platform/forge/) app. The app is distributed privately via an install link provided by Nullify — it is not listed on the Atlassian Marketplace, and there is no app descriptor to upload or developer mode to enable.

## Install the Forge app

1. **Request an install link.** Your Nullify representative will provide an install link for the Nullify Forge app.
2. **Open the install link as a Jira site admin.** A Jira site administrator opens the link (or installs the app from the Atlassian admin console) and is taken to the Atlassian consent screen.
3. **Approve the requested scopes.** The consent screen lists the permissions the app needs. Approve them to continue:
   * Read the app system token
   * Read Jira work
   * Write Jira work
4. **Link the install to your tenant.** Once the app is installed, Nullify links the Forge installation to your tenant to complete setup.

That's it — there is no manual webhook to create. The Forge app declares its event triggers in its manifest, so inbound Jira issue and comment events (created, updated, and commented) flow to Nullify automatically. This is what lets Nullify reply to comments and keep tickets in sync.

## Issue Type

Each Jira issue has an issue type (for example: Bug, Task, or Story). By default, Nullify creates tickets using the **Task** issue type.

If you want Nullify to use a different issue type, set it in the Jira integration settings in the Nullify dashboard (see [Configuration](/connectors/jira/configuration)). The issue type you choose must exist in the target project.


# Configuration

Nullify creates a new issue in Jira for each finding above the configured threshold. The Jira connector is configured from the Nullify dashboard under **Configure -> Ticketing** (and the per-integration Jira settings) — there is no `.nullify.yml` file.

In the Jira integration settings you set the **Project Key** and the **Issue Type**:

* **Project Key** is the key of the target Jira project. This is the same key that appears in the URL when you are in the project.
* **Issue Type** is the type of issue Nullify creates. The default is **Task**. You can find the available issue types in your Jira project settings under Issues -> Types (the URL is of the form `https://your-team.atlassian.net/plugins/servlet/project-config/YOUR-PROJECT-KEY/issuetypes`).

You may prefer to create a dedicated issue type for Nullify's findings if you want to track tickets opened by Nullify separately. Create it in Jira first, then select it in the dashboard.

## Optional Configuration

### Severity and Priority Threshold

Automatic ticket creation is **off by default** — enable it per finding type under **Configure -> Ticketing**, where you also set the threshold at which a ticket is created.

For example, you might want the issues dashboard to include all findings of severity MEDIUM or higher, but only create a Jira ticket for findings that are URGENT. The priorities you can set a ticket threshold to are `URGENT` and `IMPORTANT` — so you might surface everything at IMPORTANT or higher on the dashboard while only ticketing URGENT findings.

{% hint style="info" %}
Nullify only auto-creates tickets for findings at **IMPORTANT** priority or above, regardless of the threshold you set. To capture a lower-priority finding, create the ticket [manually](/connectors/jira/manual_creation) from the dashboard.
{% endhint %}

### On Fix

Nullify closes the ticket in Jira when the underlying finding is fixed. By default, the ticket transitions to the last (right-most) column on your board. If you want a different target status when a finding is fixed, set the fix transition in the Jira integration settings to the status you want. The match is case-insensitive, and the target status must already exist in the project workflow.

### Scoping automatic creation

Because automatic ticket creation is enabled per finding type, you can keep it on for the finding types that matter and off for the rest. A typical use case is enabling it globally but turning it off for a specific repository — for example, an internal tool or script that is not used in production.

You can also leave automatic creation off while keeping the project key and issue type configured, so you can still *manually* create tickets from the dashboard when needed.


# Assignee Rules

You can set a default assignee for Jira tickets in the Jira integration settings in the Nullify dashboard.

A Jira Cloud user is identified by an **account ID** — an alphanumeric string such as `5b10a2844c20165700ede21g` (not a numeric id). You can find a user's account ID in their Jira profile: open the user's profile and copy the ID from the URL, or use the Jira admin user management page.

The default assignee is optional. If you do not set one, or the assignee details are invalid, the ticket is still created but left unassigned.

When configuring the default assignee, provide the user's account ID so Nullify assigns the ticket to exactly that user. You may also record the user's display name alongside it for readability, but the account ID is what determines the assignee.


# Priorities

Nullify automatically assigns a priority to each Jira ticket based on the finding's priority. By default it maps to the standard Jira priorities of `highest`, `high`, `medium`, and `low`.

If your Jira project uses a different priority scheme, you can configure the mapping in the Jira integration settings in the Nullify dashboard. The mapping is optional — if you do not configure one, Nullify uses the default Jira priorities above.

The mapping goes from Nullify's priority taxonomy to your Jira priorities. Nullify's priorities are `URGENT`, `IMPORTANT`, `MEDIUM`, and `LOW` (note these are Nullify's priority labels, not Jira severity terms like CRITICAL/HIGH). For each Nullify priority you choose the Jira priority you want the ticket to use, for example:

| Nullify priority | Jira priority |
| ---------------- | ------------- |
| URGENT           | Critical      |
| IMPORTANT        | Important     |
| MEDIUM           | Routine       |
| LOW              | Low           |

The priority mapping you set in the dashboard applies to all tickets Nullify creates in that Jira project, so you can keep a single consistent priority scheme across your organization.


# Manual Ticket Creation

## Creating Tickets via the Dashboard

If you have disabled automatic ticket creation, you can still create tickets manually via the Nullify dashboard. This can be useful in several scenarios:

* You want to have more control over which vulnerabilities are tracked in Jira.
* Automatic ticket creation is enabled on some repositories, but you still want to manually create tickets for other repositories as needed.
* You have configured automatic ticket creation for only HIGH and URGENT findings, but you still want to create a ticket for a MEDIUM or LOW severity finding as needed.
* You need to create a ticket for a specific vulnerability that requires special attention or additional context not covered by the automatic creation rules.

### Steps to Manually Create a Ticket

1. **Navigate to the Vulnerabilities Dashboard**: Go to the Nullify dashboard and navigate to the triage page.
2. **Select the Vulnerability**: Find the vulnerability for which you want to create a Jira ticket. Click on the vulnerability to view more details.
3. **Create Ticket**: In the vulnerability details view, you will see an option to create a Jira ticket (if the ticket does not already exist). Click on the "Create Ticket" button. The ticket will be created using the default settings, and a link to the ticket will be shown in the dashboard. You can open this ticket to perform additional actions such as:

* Assigning the ticket to a specific user
* Changing the project key or issue type
* Modifying the severity

<figure><img src="/files/VwDVRcY4GtYzyrnHkkCw" alt=""><figcaption><p>Create Ticket Button</p></figcaption></figure>

### Configuration

Even when automatic ticket creation is disabled, make sure the Jira integration has a project key and issue type configured in the dashboard. Manually created tickets use these global default settings from the dashboard Jira configuration.


# Linear

## Overview

Nullify can be configured to integrate with Linear to automatically create and update issues based on the findings in your project. It is Nullify's second ticketing provider after [Jira](/connectors/jira).

When a finding crosses the configured threshold, Nullify files a Linear issue in your chosen team. As work progresses, Nullify keeps the issue in sync — and as your team moves the issue through its workflow in Linear, those status changes flow back into Nullify so the finding's ticket state stays accurate on both sides.

Nullify posts updates asynchronously so developers stay inside their existing workflows.

## Installation

Nullify connects to Linear over OAuth 2.0 using a Nullify-owned Linear app — there is nothing to install from the Linear marketplace and no app descriptor to upload.

### Connect your Linear workspace

1. In the Nullify dashboard, go to **Configure -> Integrations** and start the Linear connection. A user with config-write permission must perform this step.
2. Nullify generates a Linear authorize URL and redirects your browser to Linear's consent screen. The app requests the `read` and `write` scopes so it can create issues, update their status, and read the issues it manages.
3. Approve the consent screen in Linear. Linear redirects your browser back to Nullify, which exchanges the authorization code for an access token and records the install against your tenant. You'll see a "Linear connected" confirmation page.

During the callback, Nullify also registers its webhook on your Linear workspace automatically (for `Issue` and `Comment` events) and stores the per-install signing secret encrypted at rest — there is no manual webhook to create. See [Webhook Sync](#webhook-sync) below.

{% hint style="info" %}
You can check whether your tenant has completed the Linear install at any time — the dashboard reflects the connection status, backed by `GET /admin/integrations/linear/oauth`.
{% endhint %}

### API reference

The OAuth flow is driven by the admin API:

* `GET /admin/integrations/linear/install` — starts the install. Returns the Linear authorize URL the browser is sent to, and persists a short-lived state token keyed to your tenant.
* `GET /admin/integrations/linear/oauth/callback` — the redirect target Linear returns to. Exchanges the code for a token, registers the webhook, and records the install.
* `GET /admin/integrations/linear/oauth` — returns whether your tenant has a completed Linear install on record.

## What it does

Once connected, Nullify creates a Linear issue for each finding above the configured threshold and updates it over the finding's lifecycle:

* **Create** — a new finding produces a new Linear issue in your configured team.
* **Status sync** — when the finding is fixed, Nullify moves the issue toward a completed state; when the issue is reopened or moved back in Linear, that reopen is reflected on the Nullify side.

Linear issues are created with the **Task** issue type by default, consistent with the rest of Nullify's ticketing.

## Configuration

The Linear connector is configured from the Nullify dashboard under **Configure -> Ticketing** (and the per-integration Linear settings) — there is no `.nullify.yml` file.

### Team

Nullify files work items against a specific Linear **team**. Set the team in the Linear integration settings; this is what tells manual and automatic ticket creation which Linear team an issue should be created in. Until a team is set, the Linear integration stays inactive even if the OAuth install has completed — the team association is the trigger that turns ticketing on.

API: `POST /admin/integrations/linear/ticket-config` with the target `teamId`. This is stored independently of the OAuth tokens, so you can connect the workspace and choose the team in either order.

### Severity and Priority Threshold

By default, Nullify creates tickets using the same severity and priority thresholds reported in the dashboard. You can also set thresholds specific to ticket creation under **Configure -> Ticketing**, per finding type.

For example, you might want the issues dashboard to include all findings of priority IMPORTANT or higher, but only create a Linear issue for findings that are URGENT. Nullify's priority taxonomy is `URGENT`, `IMPORTANT`, `MEDIUM`, and `LOW`, so you can surface everything at IMPORTANT or higher on the dashboard while only ticketing URGENT findings.

### Disable

You can disable automatic Linear ticket creation from the dashboard. A typical use case is when you have the Linear connector configured globally but want to turn off automatic ticket creation for a specific repository — for example, an internal tool or script that is not used in production.

You can also disable *automatic* ticket creation while keeping the team configured, so that you can still *manually* create tickets from the dashboard when needed.

## Webhook Sync

Status changes you make in Linear are synced back into Nullify automatically through the webhook that Nullify registered during install. The webhook is registered for `Issue` and `Comment` events; Nullify processes the changes that matter for keeping finding tickets accurate:

* **Workflow-state changes** — when an issue moves to a new workflow state, Nullify maps Linear's state type (for example a `completed` or `cancelled` state) to the corresponding internal ticket status and updates the finding's ticket. A move into a terminal state is treated as a close; a move back out is treated as a reopen. Tenants running a custom Linear workflow can override the default state mapping per tenant.
* **Assignee changes** — assignee updates on a tracked issue are forwarded so the finding's ticket reflects who currently owns it.

Edits that don't affect workflow state or assignee (label-only changes, comment-only events, title edits) are ignored.

Each delivery is verified with an HMAC-SHA256 signature against the per-install signing secret captured at install time. Deliveries whose signature does not match are rejected — Nullify never processes an unverified state change for a workspace with a configured secret. Deliveries are also de-duplicated so a Linear retry of the same event is not applied twice.

{% hint style="info" %}
Linear routes the inbound events to Nullify by your workspace's organization ID, which Nullify resolves to your tenant from the OAuth install record. No additional setup is required on your side.
{% endhint %}


# Compass

## Overview

Connect Nullify to [Atlassian Compass](https://www.atlassian.com/software/compass) to import your teams and the components they own (repositories, services, and other assets). Nullify uses this ownership model to route findings, fix pull requests, and escalations to the right team — so security work lands with the people who own the affected code.

Compass data syncs periodically in the background, and Nullify posts its updates asynchronously so developers stay inside their existing workflows.


# Install

1. Open the **Nullify Compass** listing in the [Atlassian Marketplace](https://marketplace.atlassian.com/) (or use the install link your Nullify representative provides) and click the **Get App** button.

<figure><img src="/files/auioWQUv4HdrobsWd408" alt=""><figcaption><p>Install Listing</p></figcaption></figure>

2. Select the Compass organization you want to install the app into and click **Install**.

<figure><img src="/files/9SwoXxvSvVAEFkie8mmC" alt=""><figcaption></figcaption></figure>

3. Go to your Compass instance in the browser.
4. In the menu bar at the top of the screen, click the **Apps** dropdown, then click **Manage your apps**.
5. Find the **Nullify Compass** app and click **Configure**.

<figure><img src="/files/y6DXtiA2wUEQcAwZKBSh" alt=""><figcaption><p>Manage Apps</p></figcaption></figure>

6. In another browser tab, go to your Nullify dashboard configure page, e.g. `https://app.<TENANT>.nullify.ai/dashboard/configure/general`.
7. Go to **Service Accounts**, create a service account, copy the token, and paste it into the Compass app configuration.
8. Copy the GitHub owner ID and paste it into the Compass app configuration.

<figure><img src="/files/PkPaLvnEP1wQ7qDGQJU1" alt=""><figcaption><p>Configure App</p></figcaption></figure>

9. Save the Compass app settings.

After all of the steps are completed, your Compass data will be synced with Nullify periodically.


# AWS

## Overview

Connect AWS to Nullify for cloud-to-code traceability. The connector deploys a least-privilege cross-account role so the platform can ingest resource metadata, evaluate exposure, and correlate findings with the code that built those resources.

## Key Benefits

* **Cloud-to-code mapping** — Tie vulnerabilities back to specific AWS accounts, regions, and IaC definitions.
* **Blast-radius awareness** — Understand which services, identities, and networks are reachable from an exposed asset.
* **Multi-account coverage** — Operate across entire AWS Organizations with consistent policy.
* **Secure access** — Uses external IDs, scoped IAM roles, and encrypted transport.

## How It Works

1. Configure connector parameters in the Nullify console.
2. Deploy the generated CloudFormation or Terraform template into each AWS account.
3. Nullify validates the role and begins ingesting metadata for cloud exposure assessments.

Full setup instructions, parameters, and troubleshooting are documented in [Configuration](/connectors/aws/configuration).

## Support

* Implementation assistance: <support@nullify.ai>
* Feature requests: [GitHub issue tracker](https://github.com/nullify-platform/feature-requests)

Telemetry and findings flow back asynchronously into Jira, Slack, and Nullify dashboards so remediation stays aligned with cloud owners.


# Configuration

## Requirements

* An active Nullify tenant.
* AWS permissions to create IAM roles and (optionally) deploy CloudFormation StackSets.
* Access to Terraform or CloudFormation tooling.

## Setup Overview

Nullify uses cross-account IAM roles to ingest metadata from your AWS accounts. The dashboard supplies the parameters you need to deploy our CloudFormation or Terraform template.

## Information You Provide

* **IAM role name** to create within your accounts (for example `nullify-cross-account-role`).
* **Target account IDs** that Nullify should monitor.

## Parameters Supplied by Nullify

* **Nullify role ARN** for the external principal.
* **External ID** to enforce a trusted relationship.
* **S3 bucket name** for encrypted data transfer when required.

All example identifiers below are placeholders—replace them with the values displayed in your tenant.

## Setup Steps

1. **Open the Nullify console**: `Configure → Connectors → AWS`.
2. **Enter your role name** and list of account IDs.
3. **Download the deployment template** (CloudFormation or Terraform) with your tenant-specific parameters embedded.
4. **Deploy the template**:
   * *Single account*: Launch via AWS Console, CLI, or your preferred IaC workflow.
   * *Multiple accounts*: Use CloudFormation StackSets or Terraform loops. Ensure the role name is consistent across accounts.
5. **Verify the connector**: return to the Nullify console to confirm that the role can be assumed.

## Deployment Modes

### Single Account

Deploy the template directly into the account hosting your workloads. Grant the cross-account role permission to discover resources in the regions you care about.

### Multi-Account / AWS Organizations

Use StackSets or Terraform to roll out the connector across member accounts. Deploy the role to both the management and member accounts so Nullify can analyse shared services and spokes.

> The current release expects the same IAM role name in every account. Contact support if you require per-account variation.

## Validation Checklist

* Account IDs are valid 12-digit numbers.
* The IAM role exists and trusts the Nullify role ARN with the provided external ID.
* The permissions policy matches the template (least privilege for discovery only).

## Troubleshooting

* **Role not found** — Confirm the StackSet or Terraform run succeeded in each account.
* **Access denied** — Verify the trust policy includes the Nullify role ARN and external ID.
* **Stale data** — Make sure CloudTrail/Config events are flowing if you rely on change detection.

## Kubernetes (optional add-on)

To relate Kubernetes workloads back to your AWS accounts, deploy the [Nullify Kubernetes Collector](https://github.com/Nullify-Platform/nullify-cloud-connector) into your clusters. See the [Kubernetes connector guide](/connectors/kubernetes) for setup; the Helm chart includes installation instructions, configuration samples, and troubleshooting guidance.


# GCP

## Overview

Connect GCP to Nullify for cloud-to-code traceability. The connector uses Workload Identity Federation (WIF) with OIDC — no long-lived service account keys, no secrets to manage.

## Key Benefits

* **Cloud-to-code mapping** — Tie vulnerabilities back to specific GCP projects, regions, and IaC definitions.
* **Blast-radius awareness** — Understand which services, identities, and networks are reachable from an exposed asset.
* **Multi-project coverage** — Operate across GCP organizations, folders, or individual projects.
* **Secure access** — Uses OIDC federation with per-tenant trust. No service account JSON keys.
* **Read-only** — Nullify cannot modify your environment, read object data, or access secret payloads.

## How It Works

1. Deploy the Nullify Terraform module in your GCP project. This creates a workload identity pool, a read-only service account, and IAM bindings.
2. Paste the `service_account_email` and `workload_identity_provider` outputs into the Nullify console.
3. Nullify validates the credentials and begins ingesting metadata for cloud exposure assessments.

Full setup instructions are documented in [Configuration](/connectors/gcp/configuration).

## Optional: Kubernetes Collector (GKE)

Deploy the Nullify k8s-collector to your GKE clusters for workload-level visibility (pods, services, deployments, ingresses).

1. Share your cluster's OIDC issuer URL with Nullify (one `gcloud` command).
2. Nullify returns a role ARN.
3. Deploy the Helm chart with the role ARN.

No GCP service account or Workload Identity binding required. See [Configuration](/connectors/gcp/configuration#kubernetes-collector-gke) for details.

## Support

* Implementation assistance: <support@nullify.ai>
* Terraform module: [nullify-cloud-connector](https://github.com/Nullify-Platform/nullify-cloud-connector/tree/main/gcp-integration-setup/terraform)


# Configuration

## Requirements

* An active Nullify tenant.
* A GCP project with permissions to create workload identity pools and service accounts.
* Terraform >= 1.3 (recommended) or `gcloud` CLI.
* For organization or folder scope: org admin access.

## Setup Overview

Nullify uses Workload Identity Federation (WIF) with OIDC to access your GCP environment. The Terraform module creates a workload identity pool, a read-only service account, and the minimum IAM bindings needed for cloud scanning.

## Parameters Supplied by Nullify

Available in the Nullify console under **Configure > Connectors > GCP**:

* **OIDC Issuer URI** — `https://gcp.nullify.ai` (production)
* **Tenant ID** — Your Nullify tenant identifier

## Information You Provide

After deploying the Terraform module:

* **Workload Identity Provider** — The full resource path from the Terraform output
* **Service Account Email** — The impersonated service account email from the Terraform output
* **Project IDs** — The GCP projects Nullify should scan

## Setup Steps

1. **Clone the Terraform module**:

   ```bash
   git clone https://github.com/Nullify-Platform/nullify-cloud-connector.git
   cd nullify-cloud-connector/gcp-integration-setup/terraform
   ```
2. **Configure variables**:

   ```bash
   cp terraform.tfvars.example terraform.tfvars
   ```

   Fill in:

   * `gcp_project_id` — Your GCP project
   * `nullify_oidc_issuer_uri` — From the Nullify console
   * `nullify_tenant_id` — From the Nullify console
   * `scope` — `"organization"`, `"folder"`, or `"projects"`
   * `organization_id` — Required for org or folder scope
3. **Deploy**:

   ```bash
   terraform init
   terraform plan
   terraform apply
   ```
4. **Configure in Nullify console**:
   * Go to **Configure > Connectors > GCP**
   * Paste the `workload_identity_provider` output
   * Paste the `service_account_email` output
   * Add your GCP project IDs
   * Click **Save** — Nullify auto-verifies the credentials

## Scope Options

| Scope          | Use When                          | Requires                        |
| -------------- | --------------------------------- | ------------------------------- |
| `organization` | Scan all projects in your GCP org | `organization_id`               |
| `folder`       | Scan all projects in a folder     | `organization_id` + `folder_id` |
| `projects`     | Scan specific projects only       | `project_ids` list              |

## Permissions

Nullify requests **read-only** access. No write, no data-plane, no code execution permissions.

**Predefined roles**: `cloudasset.viewer`, `iam.securityReviewer`, `compute.viewer`, `container.clusterViewer`, `cloudsql.viewer`, `spanner.viewer`, `cloudkms.viewer`, `logging.viewer`, `run.viewer`, `cloudfunctions.viewer`, `appengine.appViewer`, `dataproc.viewer`, `dataflow.viewer`, `pubsub.viewer`

**Custom role** (`nullifyCloudConnector`): Read-only permissions for Cloud Armor, VPC Service Controls, org policies, AlloyDB, Filestore, Memorystore, Artifact Registry, Cloud DNS, and API Gateway.

Full permission justification: [permissions.md](https://github.com/Nullify-Platform/nullify-cloud-connector/blob/main/gcp-integration-setup/docs/permissions.md)

## Revoking Access

```bash
terraform destroy
```

This deletes the workload identity pool, service account, and all IAM bindings.

***

## Kubernetes Collector (GKE)

Deploy the Nullify k8s-collector to GKE clusters for workload-level visibility.

### Prerequisites

* Any GKE cluster (Standard, Autopilot, private, or public)
* Kubernetes 1.22+
* Helm v3
* No special GKE configuration required

### Setup

1. **Get your cluster's OIDC issuer URL**:

   ```bash
   gcloud container clusters describe CLUSTER --zone ZONE \
     --format='value(selfLink)'
   ```
2. **Register in Nullify console**:
   * Go to **Configure > Connectors > GCP > GKE Clusters**
   * Add the OIDC issuer URL
   * Click **Save**
   * Copy the **K8s Collector Role ARN** displayed on the card
3. **Deploy the Helm chart**:

   ```bash
   helm repo add nullify https://nullify-platform.github.io/nullify-cloud-connector/
   helm repo update

   helm install nullify-collector nullify/nullify-k8s-collector \
     --namespace nullify --create-namespace \
     --set cloudProvider=gcp \
     --set collector.clusterName=my-gke-cluster \
     --set collector.aws.region=us-east-1 \
     --set collector.s3.bucket=your-nullify-bucket \
     --set collector.kms.keyArn=arn:aws:kms:us-east-1:123456789012:key/... \
     --set collector.gke.awsRoleArn=arn:aws:iam::123456789012:role/...
   ```

   The `collector.aws.region`, `collector.s3.bucket`, `collector.kms.keyArn`, and role ARN values all come from the Nullify **Configure → Connectors** page. The collector runs on a schedule (daily by default); adjust `collector.schedule` if you need a different cadence.

### What the Collector Gathers

* Cluster metadata (version, node pools)
* Namespaces, pods, deployments, statefulsets, daemonsets
* Services, ingresses, endpoints
* RBAC (roles, role bindings, service accounts)
* Network policies
* Persistent volumes and claims

Data is uploaded to Nullify's S3 bucket encrypted in transit. No cluster modifications are made.

## Troubleshooting

| Symptom                                                    | Likely Cause                                                                             |
| ---------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| Verify returns red with `permission denied`                | The Nullify SA is missing IAM bindings. Re-run `terraform apply`.                        |
| Verify returns red with `invalid token`                    | `nullify_oidc_issuer_uri` doesn't match. Use `https://gcp.nullify.ai` for production.    |
| k8s-collector fails with `AssumeRoleWithWebIdentity` error | The cluster OIDC URL wasn't registered in the Nullify console, or the role ARN is wrong. |
| k8s-collector pod stuck in `Pending`                       | Insufficient CPU/memory on the node. Reduce resource requests in Helm values.            |
| Connector shows `Not Verified`                             | Click Verify in the console, or re-save the settings to trigger auto-verify.             |


# Kubernetes

## Overview

Connect Kubernetes to Nullify to audit your clusters for misconfiguration. Nullify evaluates a dedicated Kubernetes ruleset — workload, RBAC, network, and cluster-configuration checks — as part of its [Cloud Audits](/capabilities/cloud-audits) (CSPM) capability, then correlates findings with the accounts and code that own those clusters.

## Key Benefits

* **Cluster misconfiguration detection** — Catch insecure workloads, over-broad RBAC, missing network policy, and weak cluster controls before they are exploited.
* **Managed and on-prem coverage** — Works for cloud-managed clusters (EKS, GKE, and AKS via your Azure account) and self-hosted / on-prem clusters.
* **Refreshes as data lands** — Findings reconcile each time a cluster's inventory arrives. The in-cluster collector uploads on a schedule (daily by default, configurable), and Nullify re-audits each time new inventory lands.
* **Read-only** — The collector reads cluster metadata through the Kubernetes API and uploads it for analysis. Nullify does not modify your cluster.

## What It Scans

Nullify ships a dedicated Kubernetes ruleset (alongside the AWS, GCP, and Azure rulesets) that runs against the collected cluster inventory. Checks span:

* **Workloads** — privileged containers, host namespace sharing (host PID / IPC / network), privilege escalation, dangerous capabilities, hostPath volumes, image pinning, and pull-policy hygiene.
* **RBAC** — cluster-admin bindings, wildcard and escalation verbs, anonymous and default-service-account bindings, and stale or over-broad role grants.
* **Network & admission** — missing default-deny network policies, overly permissive ingress, and admission/webhook configuration weaknesses.
* **Cluster configuration** — etcd encryption, control-plane settings, and config-map hygiene (sensitive data, embedded credentials).

Each detection becomes a CSPM finding that is AI-triaged for false-positive likelihood and priority, the same as any AWS, GCP, or Azure cloud finding. See [Cloud Audits](/capabilities/cloud-audits) for the cross-provider view.

## How It Works

Nullify supports two scanning paths depending on cluster type:

1. **Managed clusters (EKS, GKE)** — Scanned platform-side using the underlying cloud account's credentials. Configure these alongside the cluster's cloud account ([AWS](/connectors/aws), [GCP](/connectors/gcp)). For AKS, connect your Azure account — contact your Nullify representative for setup.
2. **On-prem / self-hosted clusters** — Scanned by the in-cluster Nullify Kubernetes collector, which walks the cluster API and uploads a cluster-inventory envelope to Nullify's secured upload area. An ingestor then validates the upload and stores it for scanning.

In both cases, a completed scan emits an event that drives the CSPM reconcile: new misconfigurations are stored and triaged, fixed ones are resolved, and reintroduced ones re-open — so cluster posture stays current without a fixed schedule.

## Collector Setup (On-Prem / Self-Hosted)

The Nullify Kubernetes collector ships in the `nullify-cloud-connector` Helm chart and runs inside your cluster. At a high level:

1. **Deploy the collector** into the target cluster via Helm. It runs on a schedule, authenticates to the Kubernetes API in-cluster (read-only), and collects cluster resources — nodes, namespaces, pods, services, deployments, RBAC, and related objects.
2. **Grant upload access.** The collector uploads its inventory envelope to Nullify's secured S3 upload area. Encryption in transit is enforced; the collector supports a KMS key for upload encryption.
3. **Whitelist the account(s).** Nullify must allow the uploading AWS account(s) to write to the bucket (and use the KMS key). See [Whitelisting Collector Accounts](#whitelisting-collector-accounts) below.

{% hint style="info" %}
The collector is read-only. It reads cluster metadata through the Kubernetes API and never writes to your cluster.
{% endhint %}

Cluster registration is managed through the admin API. A site administrator declares each cluster (name, type, and — for managed clusters — the cloud account ID and region) and optionally the Nullify role ARN used to reach the cluster.

## Whitelisting Collector Accounts

Collector uploads land in a Nullify-managed bucket whose access policy is allowlisted per AWS account. The platform normally reconciles this allowlist automatically when cloud settings are saved, but a dedicated endpoint exists to (re)apply it on demand — useful during onboarding or if the bucket/KMS policy drifts:

```http
POST /admin/integrations/cloud/k8s/whitelist
```

```json
{
  "accountIds": ["123456789012"]
}
```

Behaviour:

* **`accountIds` (optional)** — The AWS account IDs allowed to upload collector data. If omitted, Nullify falls back to every account stored on the tenant's AWS cloud settings.
* The handler reconciles both the **upload bucket policy** and, when configured, the **KMS key policy**, and reports which were updated. If no KMS key ARN is configured, the response reports `"kmsSkipped": true` with `kmsPolicyUpdated: false` and no `kmsError`.
* Sending an empty request when no AWS accounts are configured is rejected as a bad request (an empty reconcile would strip the allowlist rather than extend it).

This endpoint requires config-write permission.

{% hint style="warning" %}
Whitelisting only grants upload access — it does not register a cluster or start a scan. Declare the cluster through cloud settings, then deploy the collector.
{% endhint %}

## Support

* Implementation assistance: <support@nullify.ai>
* Feature requests: [GitHub issue tracker](https://github.com/nullify-platform/feature-requests)

Findings flow asynchronously into Jira, Slack, and the Nullify dashboards so remediation stays aligned with cluster and cloud owners.


# Buildkite

## Overview

The Buildkite connector gives Nullify eyes on your CI. When Nullify opens an autofix pull request, your Buildkite pipeline runs against it like any other change — and if that build fails, Nullify reads the failure and fixes its own work before a human ever has to look. Once connected, Nullify can:

* **See your Buildkite builds** on the pull requests it opens, including pass/fail status.
* **Read the logs of a failed build** straight from the Buildkite API to understand *why* CI broke.
* **Iterate on the fix automatically**, feeding the real CI failure back to the autofix agent until the build is green.

The result is an autofix loop that's accountable to your actual pipeline — linting, tests, type checks, build steps and all — not just to Nullify's own checks. Teams that run Buildkite for CI alongside GitHub for source control get fixes that are already CI-clean by the time they land in review.

## How it works

Nullify never sits in the critical path of your pipeline and never changes how your builds run. It watches, reads, and iterates:

1. **Nullify pushes an autofix commit** to a pull request.
2. **Buildkite runs your pipeline** against that commit, exactly as it would for any developer push. (If your org gates *who* can trigger builds, Nullify's Buildkite user needs a seat on an allowed team first — see [Configuration → Step 5](/connectors/buildkite/configuration#build-creator-access).)
3. **Buildkite reports the result to GitHub** as a commit status (this is Buildkite's standard GitHub integration).
4. **Nullify detects the status** through its GitHub App — including when the build fails.
5. **Nullify fetches the failed job logs** from the Buildkite API using your connected token.
6. **The autofix agent iterates**, using the real failure output as context, and pushes a new commit — which kicks off the loop again until CI is green.

```
Autofix commit ─▶ Buildkite pipeline runs ─▶ Build fails
                                                  │
                          GitHub commit status ◀──┘
                                   │
                          Nullify detects failure
                                   │
                       Fetches Buildkite job logs ─▶ Agent iterates ─▶ new commit ↺
```

The integration is **read-only on your CI**: Nullify reads build status and logs. It does not trigger, cancel, or modify your pipelines, and it does not write anything back to Buildkite.

## Prerequisites

Before you start, make sure you have:

* A **Buildkite organization** with at least one pipeline that runs on your pull requests.
* The [**Buildkite GitHub App**](https://buildkite.com/docs/integrations/github#connect-your-buildkite-account-to-github) installed on your GitHub organization, with **commit statuses** enabled (this is how build results reach GitHub — and therefore Nullify).
* **GitHub already connected to Nullify.** Buildkite layers on top of the GitHub connection; it is not a standalone source-control integration.
* A **Buildkite API access token** with just two scopes — `read_builds` and `read_build_logs`.
* **If your organization restricts who can trigger builds** (build-creator team gating): the ability to invite Nullify's Buildkite user to your org and add it to an allowed team — see [Configuration → Step 5](/connectors/buildkite/configuration#build-creator-access). Without this, Nullify's autofix commits never run a build in a gated org, so there's nothing for Nullify to read.

{% hint style="info" %}
**Use a dedicated service account for the API token, not a personal one.** Create a Buildkite user that exists only for Nullify (for example, `nullify-ci@yourcompany.com`), give it access to the organization, and mint the token from that account. This keeps the integration alive when people change teams or leave, scopes Nullify's access to exactly what that user can see, and makes the token easy to audit and rotate.

This token account is **separate** from Nullify's own Buildkite user (`integrations-bot@nullify.ai`) in [Configuration → Step 5](/connectors/buildkite/configuration#build-creator-access) — that user exists so Nullify's *commits* can trigger your builds in gated orgs, and is unrelated to the read token.
{% endhint %}

## Next steps

Follow the [**Configuration**](/connectors/buildkite/configuration) guide to create the token and connect your Buildkite organization.


# Configuration

Connecting Buildkite takes about five minutes and three things: a Buildkite API token, your organization slug, and a one-time GitHub permission acknowledgement. This guide walks through all three — plus an optional step for organizations that restrict who can trigger builds.

## Step 1: Create a Buildkite API token

We recommend minting the token from a **dedicated service-account user** rather than a personal account — see the [callout in the overview](/connectors/buildkite) for why. Log in to Buildkite **as that user**, then:

1. Go to **Personal Settings → API Access Tokens** (`https://buildkite.com/user/api-access-tokens`).
2. Click **New API Access Token**.
3. Give it a recognizable name, e.g. `nullify-ci-feedback`.
4. Scope it to the **single organization** Nullify should see.
5. Enable **only** these two scopes — nothing else:
   * `read_builds`
   * `read_build_logs`
6. Click **Create Token** and copy the value. Buildkite API tokens are in the form `bkua_…`.

{% hint style="warning" %}
The token is shown **only once**. Copy it before you leave the page — if you lose it, you'll need to create a new one.
{% endhint %}

{% hint style="info" %}
**Least privilege.** Nullify only ever *reads* build status and logs, so it needs nothing beyond `read_builds` and `read_build_logs`. Don't grant write, agent, pipeline-management, or token-management scopes.
{% endhint %}

## Step 2: Connect Buildkite in Nullify

1. In the Nullify dashboard, go to **Configure → Connectors**.
2. Find the **Buildkite** card and click **Connect Buildkite**.
3. Fill in the two fields:
   * **Organization slug** — the identifier from your Buildkite URL, `buildkite.com/<org-slug>`. It's lowercase letters, numbers, and hyphens (e.g. `my-org`).
   * **API token** — paste the `bkua_…` token from Step 1.
4. Click **Save**.

The Buildkite card now shows **Active** with your organization name, and unlocks **Autofix CI Feedback**.

{% hint style="info" %}
**"Active" means saved, not yet proven.** Nullify checks the *format* of the token and slug when you save, but it confirms the credentials actually work the first time it reads a real build. Run the end-to-end check in [Step 6](#step-6-verify) to be sure everything is wired up.
{% endhint %}

## Step 3: Make sure Buildkite reports to GitHub

Nullify learns about your builds through the commit status Buildkite posts to GitHub. If that status isn't being published, Nullify has nothing to react to.

1. In Buildkite, go to **Organization Settings → Repository Providers → GitHub**.
2. Confirm the **Buildkite GitHub App** is installed (not the legacy OAuth integration).
3. In each relevant **pipeline's settings**, confirm **Publish commit statuses** (under GitHub settings) is enabled.

You can sanity-check this independently of Nullify: open any recent pull request that triggered a build and confirm a `buildkite/…` status check appears on it.

## Step 4: Acknowledge the GitHub App permission <a href="#github-app-permissions" id="github-app-permissions"></a>

To receive those commit-status events, Nullify's GitHub App needs two things:

* **Repository permission:** *Commit statuses* → **Read-only**.
* **Event subscription:** the **Status** event.

These are already configured on the Nullify GitHub App. The only action on your side is a one-time acknowledgement: GitHub asks each organization owner to **accept the updated permissions**, shown as an email and a banner on your organization's installed-apps page.

{% hint style="warning" %}
Until your organization owner accepts the updated permission, GitHub won't send build-status events to Nullify — so the autofix loop won't see your Buildkite results, even though the connector shows **Active**.
{% endhint %}

## Step 5: Let Nullify's commits trigger builds <a href="#build-creator-access" id="build-creator-access"></a>

**Skip this step unless your Buildkite organization restricts who can trigger builds.** Some orgs gate builds by the creator's team — for example an agent `environment` hook or a pipeline conditional that checks [`BUILDKITE_BUILD_CREATOR_TEAMS`](https://buildkite.com/docs/pipelines/configure/environment-variables) (or `build.creator.teams`) and fails any build whose creator isn't on an allowed team. If your org runs every push regardless of who created it, jump to [Step 6](#step-6-verify).

When Nullify opens an autofix pull request, the commit is authored by Nullify's GitHub App, using the email `<id>+nullify[bot]@users.noreply.github.com`. Buildkite maps that commit author to a Buildkite user by email and fills `BUILDKITE_BUILD_CREATOR_TEAMS` from that user's teams. So in a gated org, Nullify's Buildkite user must exist in your org and sit on an allowed team — otherwise the build is blocked before it starts, no commit status is posted, and the autofix loop never sees your CI (even though the connector shows **Active**).

1. **Invite the Nullify user.** Invite `integrations-bot@nullify.ai` to your Buildkite organization. It is a Buildkite-managed service account, like Dependabot or Renovate: it auto-accepts the invite, skips billing, and cannot log in.
2. **Add it to an allowed, non-private team.** Put the user on the team(s) your build-creator gate accepts. The team must be **non-private** — `BUILDKITE_BUILD_CREATOR_TEAMS` only lists non-private team slugs, so membership of a private team won't satisfy the check.

{% hint style="info" %}
The gate matches on the commit's **author email**, which Buildkite treats as unverified — Nullify's commits do **not** need to be GPG-signed for this to work. (This is also why the account is associated with the `…+nullify[bot]@users.noreply.github.com` address rather than a regular mailbox.) This Buildkite user is **separate** from the API-token service account in Step 1: the token account authenticates Nullify's log reads; this user lets Nullify's commits run a build.
{% endhint %}

## Step 6: Verify <a href="#step-6-verify" id="step-6-verify"></a>

End-to-end, the simplest way to confirm everything works:

1. Push a commit to an open pull request so Buildkite runs a build.
2. Confirm the `buildkite/…` status appears on the GitHub PR (this proves Steps 3–4).
3. If a build fails on a Nullify autofix PR, watch Nullify pick up the failure and push a follow-up commit — that confirms log fetching and iteration are working.

## Troubleshooting

| Symptom                                                                      | Likely cause and fix                                                                                                                                                           |
| ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Autofix PR opened, but Buildkite never runs a build** on Nullify's commits | Your org gates build creators by team, and Nullify's Buildkite user isn't invited, isn't on an allowed team, or is on a **private** team. See [Step 5](#build-creator-access). |
| Connector saved but autofix never reacts to Buildkite failures               | The GitHub permission acknowledgement (Step 4) is still pending, or commit statuses aren't being published (Step 3). Check both.                                               |
| **No `buildkite/…` status on the GitHub PR**                                 | The Buildkite GitHub App isn't installed, or the pipeline doesn't have **Publish commit statuses** enabled. See Step 3.                                                        |
| Builds are detected but **logs aren't fetched**                              | The token is missing the `read_build_logs` scope, or it was created in the wrong organization. Re-check Step 1 and rotate the token if needed.                                 |
| **Status shows Active but nothing happens**                                  | "Active" only means the token and slug were saved. Verify the token is valid and scoped to the right org by running the check in Step 5.                                       |
| Token was created by someone who has since left                              | The token stops working when their account is deactivated. Re-mint it from a dedicated service-account user (see the overview) and update it in Step 2.                        |

## Rotating the token

1. Create a new token in Buildkite (Step 1) — from the same service-account user.
2. In Nullify, go to **Configure → Connectors → Buildkite → Update** and paste the new token.
3. Revoke the old token in Buildkite.

There's no downtime: Nullify uses the new token as soon as you save it.

## Removing the integration

1. Go to **Configure → Connectors → Buildkite**.
2. Click **Delete**.

This removes your stored token and organization slug from Nullify. Your Buildkite pipelines are untouched and keep running exactly as before — disconnecting only stops Nullify from reading build status and logs.


# Jenkins

## Overview

The Jenkins connector gives Nullify eyes on your CI. When Nullify opens an autofix pull request, your Jenkins job runs against it like any other change — and if that build fails, Nullify reads the failure and fixes its own work before a human ever has to look. Once connected, Nullify can:

* **See your Jenkins builds** on the pull requests it opens, including pass/fail status.
* **Read the console output of a failed build** straight from the Jenkins API to understand *why* CI broke.
* **Iterate on the fix automatically**, feeding the real CI failure back to the autofix agent until the build is green.

The result is an autofix loop that's accountable to your actual pipeline — linting, tests, type checks, build steps and all — not just to Nullify's own checks. Teams that run Jenkins for CI alongside GitHub for source control get fixes that are already CI-clean by the time they land in review.

## How it works

Nullify never sits in the critical path of your pipeline and never changes how your builds run. It watches, reads, and iterates:

1. **Nullify pushes an autofix commit** to a pull request.
2. **Jenkins runs your job** against that commit, exactly as it would for any developer push.
3. **Jenkins reports the result to GitHub** as a commit status (this is Jenkins' standard GitHub integration — for example the GitHub Branch Source plugin, which posts a `continuous-integration/jenkins/…` status).
4. **Nullify detects the status** through its GitHub App — including when the build fails.
5. **Nullify fetches the failed build's console log** from the Jenkins API using your connected URL, username, and token. The build's console URL is taken from the status event and **pinned to your configured Jenkins host** before any request is made.
6. **The autofix agent iterates**, using the real failure output as context, and pushes a new commit — which kicks off the loop again until CI is green.

```
Autofix commit ─▶ Jenkins job runs ─▶ Build fails
                                          │
                  GitHub commit status ◀──┘
                           │
                  Nullify detects failure
                           │
               Fetches Jenkins console log ─▶ Agent iterates ─▶ new commit ↺
```

The integration is **read-only on your CI**: Nullify reads build status and console output. It does not trigger, cancel, or modify your jobs, and it does not write anything back to Jenkins.

{% hint style="info" %}
**How Nullify recognises a Jenkins build.** Nullify matches the GitHub commit-status context (`continuous-integration/jenkins…`) reported by your Jenkins GitHub integration. If your jobs publish statuses under a different context string, let your Nullify representative know so detection can be confirmed.
{% endhint %}

## Prerequisites

Before you start, make sure you have:

* A **Jenkins instance** reachable over HTTPS, running at least one job against your pull requests. The instance must be reachable from Nullify — Nullify rejects URLs that resolve to internal, loopback, or otherwise private addresses.
* Your Jenkins instance **posting commit statuses to GitHub** (for example via the GitHub Branch Source or GitHub plugin), so build results reach GitHub — and therefore Nullify.
* **GitHub already connected to Nullify.** Jenkins layers on top of the GitHub connection; it is not a standalone source-control integration.
* A **Jenkins user and API token** that can read the relevant jobs' console output. Nullify authenticates to the Jenkins API with HTTP basic auth using this username and token.

{% hint style="info" %}
**Use a dedicated service account for the API token, not a personal one.** Create a Jenkins user that exists only for Nullify, grant it read access to the relevant jobs, and mint the API token from that account (**Manage Jenkins → Users →&#x20;*****that user*****&#x20;→ Configure → API Token**). This keeps the integration alive when people change teams or leave, scopes Nullify's access to exactly what that user can see, and makes the token easy to audit and rotate.
{% endhint %}

## Configuration

Configure Jenkins through the Nullify dashboard (**Configure → Connectors → Jenkins**) or the admin API. The credentials you provide are stored encrypted in AWS SSM Parameter Store — the API token as a `SecureString`; Nullify never returns the token back to you after it is saved.

### Connect via the admin API

Store the URL, username, and API token:

```http
POST /admin/integrations/jenkins/token
Content-Type: application/json

{
  "url": "https://jenkins.yourcompany.com",
  "username": "nullify-ci",
  "apiToken": "<jenkins-api-token>"
}
```

All three fields are required. The `url` must be a valid `http`/`https` URL and must not target an internal or loopback host.

Check whether a Jenkins integration is configured:

```http
GET /admin/integrations/jenkins
```

```json
{ "configured": true }
```

Remove the integration (deletes the stored URL, username, and token):

```http
DELETE /admin/integrations/jenkins
```

{% hint style="info" %}
Re-posting to `/admin/integrations/jenkins/token` overwrites the stored URL, username, and token in place — that's also how you **rotate** the API token. There's no downtime: Nullify uses the new credentials the next time it reads a build.
{% endhint %}

## What data flows

| Direction             | Data                                                                                                                                                                     |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **GitHub → Nullify**  | Commit-status events for Jenkins builds on Nullify's pull requests (pass/fail and the build's target URL), via the Nullify GitHub App.                                   |
| **Jenkins → Nullify** | The plain-text console log of a **failed** build, fetched on demand from `…/consoleText` on your configured Jenkins host. Log reads are capped in size and time-bounded. |
| **Nullify → Jenkins** | Nothing. Nullify only issues authenticated read requests for console output; it never triggers, cancels, or configures jobs.                                             |

## Removing the integration

Delete the connector from **Configure → Connectors → Jenkins**, or call `DELETE /admin/integrations/jenkins`. This removes your stored URL, username, and token from Nullify. Your Jenkins jobs are untouched and keep running exactly as before — disconnecting only stops Nullify from reading build status and console output.


# CircleCI

## Overview

The CircleCI connector lets Nullify see your CI on the pull requests it opens. When Nullify pushes an autofix commit, your CircleCI pipeline runs against it like any other change, and Nullify learns the pass/fail result alongside the fix. Once connected, Nullify can:

* **See your CircleCI pipelines** on the pull requests it opens, including pass/fail status.
* **Hold an API token** for your CircleCI organization, stored encrypted, so Nullify can authenticate to the CircleCI API on your behalf.

The goal is the same autofix loop Nullify runs for other CI providers — fixes that are accountable to your actual pipeline, not just to Nullify's own checks.

{% hint style="info" %}
**Scope today.** Nullify already **detects** CircleCI builds from the GitHub commit status (`ci/circleci…`, or a `circleci.com` / `app.circleci.com` target URL) and stores your token. Automated **failed-build log fetching and autofix iteration** is live for Buildkite and Jenkins today; CircleCI log ingestion is rolling out. Connecting now provisions the credential so the loop activates automatically once enabled for your tenant — talk to your Nullify representative for current availability.
{% endhint %}

## How it works

Nullify never sits in the critical path of your pipeline and never changes how your builds run. It watches and reads:

1. **Nullify pushes an autofix commit** to a pull request.
2. **CircleCI runs your pipeline** against that commit, exactly as it would for any developer push.
3. **CircleCI reports the result to GitHub** as a commit status (this is CircleCI's standard GitHub integration), and Nullify detects it through its GitHub App.
4. **Nullify authenticates to the CircleCI API** with your stored token to read the failed pipeline's output and feed it back to the autofix agent.

The integration is **read-only on your CI**: Nullify reads build status and logs. It does not trigger, cancel, or modify your pipelines, and it does not write anything back to CircleCI.

## Prerequisites

Before you start, make sure you have:

* A **CircleCI organization** with at least one pipeline that runs on your pull requests.
* Your CircleCI organization **posting commit statuses to GitHub** (CircleCI's standard GitHub integration), so build results reach GitHub — and therefore Nullify.
* **GitHub already connected to Nullify.** CircleCI layers on top of the GitHub connection; it is not a standalone source-control integration.
* A **CircleCI personal API token** that can read the relevant projects. Nullify uses this single token to authenticate to the CircleCI API.

{% hint style="info" %}
**Use a dedicated service account for the API token, not a personal one.** Create a CircleCI user that exists only for Nullify, give it access to the relevant organization and projects, and mint the personal API token from that account (**User Settings → Personal API Tokens**). This keeps the integration alive when people change teams or leave, scopes Nullify's access to exactly what that user can see, and makes the token easy to audit and rotate.
{% endhint %}

## Configuration

Configure CircleCI through the Nullify dashboard (**Configure → Connectors → CircleCI**) or the admin API. The token you provide is stored encrypted in AWS SSM Parameter Store as a `SecureString` — Nullify never returns it back to you after it is saved.

### Connect via the admin API

Store the API token:

```http
POST /admin/integrations/circleci/token
Content-Type: application/json

{
  "apiToken": "<circleci-personal-api-token>"
}
```

`apiToken` is required.

Check whether a CircleCI integration is configured:

```http
GET /admin/integrations/circleci
```

```json
{ "configured": true }
```

Remove the integration (deletes the stored token):

```http
DELETE /admin/integrations/circleci
```

{% hint style="info" %}
Re-posting to `/admin/integrations/circleci/token` overwrites the stored token in place — that's also how you **rotate** it. There's no downtime: Nullify uses the new token the next time it reads a build.
{% endhint %}

## What data flows

| Direction              | Data                                                                                                                                    |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| **GitHub → Nullify**   | Commit-status events for CircleCI builds on Nullify's pull requests (pass/fail and the build's target URL), via the Nullify GitHub App. |
| **CircleCI → Nullify** | Authenticated reads against the CircleCI API using your stored token (failed-build log ingestion rolling out — see the note above).     |
| **Nullify → CircleCI** | Nothing. Nullify only issues authenticated read requests; it never triggers, cancels, or configures pipelines.                          |

## Removing the integration

Delete the connector from **Configure → Connectors → CircleCI**, or call `DELETE /admin/integrations/circleci`. This removes your stored token from Nullify. Your CircleCI pipelines are untouched and keep running exactly as before — disconnecting only stops Nullify from reading build status and logs.


# OIDC

## Overview

Nullify's OIDC connectors establish **keyless trust** between Nullify and your identity, version-control, and cloud providers. Instead of storing long-lived credentials, Nullify runs OpenID Connect (OIDC) issuers that mint or publish short-lived, signed tokens. The relying party (AWS Cognito for sign-in, or a cloud provider's federation service for infrastructure access) verifies those tokens against a public key, so no static secret is ever shared.

> Each connector is a small OpenID Connect provider. Some bridge a provider's OAuth2 endpoints into a standard OIDC flow used for single sign-on; one publishes only the public signing key used for cloud workload-identity federation.

## How OIDC trust works

There are two patterns across the connectors:

* **OAuth-to-OIDC sign-in bridges** (GitHub, Azure, Bitbucket, GitLab) — These providers expose OAuth2 but not native OpenID Connect. The bridge wraps their OAuth2 flow, signing a standard ID token (RS256) so that AWS Cognito and similar relying parties can authenticate users and capture provider context (installations, workspaces, organizations, groups) during sign-in. Each exposes the standard endpoints `/.well-known/openid-configuration`, `/authorize`, `/token`, `/userinfo`, and `/jwks.json`.
* **Cloud workload-identity federation** (GCP) — Nullify publishes only a discovery document and a JWKS endpoint. Your cloud project trusts the Nullify issuer, and a federation service exchanges a Nullify-signed token for a short-lived cloud credential scoped to your tenant — no service-account key is ever exported.

In both patterns, JWT signing uses an RSA key pair whose private key stays in Nullify's AWS SSM Parameter Store and whose public key is served from the issuer's `/jwks.json` (or `/.well-known/openid-configuration`).

## Supported providers

| Provider      | Issuer URL                     | Purpose                                                                                                                |
| ------------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------- |
| **GitHub**    | `https://github.nullify.ai`    | OAuth-to-OIDC bridge for sign-in; enriches claims with GitHub App installation and owner data.                         |
| **Azure**     | `https://azure.nullify.ai`     | OAuth-to-OIDC bridge for sign-in; captures Azure DevOps organizations and projects via Microsoft Graph + Azure DevOps. |
| **GCP**       | `https://gcp.nullify.ai`       | Workload Identity Federation issuer; publishes the public key Google STS uses to verify Nullify-signed subject tokens. |
| **GitLab**    | `https://gitlab.nullify.ai`    | OAuth-to-OIDC bridge for sign-in; enriches claims with GitLab group membership.                                        |
| **Bitbucket** | `https://bitbucket.nullify.ai` | OAuth-to-OIDC bridge for sign-in; enriches claims with Bitbucket workspace and repository data.                        |

{% hint style="info" %}
Issuer URLs above are the production hosts. Development environments use the equivalent `*.dev.nullify.ai` subdomain (for example, `https://gcp.dev.nullify.ai`).
{% endhint %}

## Provider notes

### GitHub

GitHub exposes OAuth2 endpoints but has no native OpenID Connect support. The GitHub connector wraps GitHub OAuth2 as an OIDC provider for use with AWS Cognito. During sign-in, `/userinfo` fetches the user and their GitHub App installations concurrently and adds `github_install_ids`, `github_owner_ids`, and `github_owner_types` claims so Nullify knows which installations a user can access. GitHub Enterprise is supported by pointing the connector at a custom GitHub base URL.

### Azure

The Azure connector bridges Azure AD OAuth2, Microsoft Graph, and Azure DevOps into a single OIDC flow, primarily to capture the Azure DevOps organizations and projects a user can access at sign-in. It runs a multi-step on-behalf-of (OBO) token flow: it exchanges the auth code, then requests OBO grants for Azure DevOps and Microsoft Graph, and bundles the resulting tokens so `/userinfo` can enumerate the user's organizations.

### GCP

Unlike the OAuth bridges, the GCP issuer performs **no** OAuth flows (no `/authorize`, `/token`, or `/userinfo`) and signs no tokens itself. It is deployed once per environment as a shared multi-tenant platform service and publishes only the discovery document and the RSA **public** key. Your project creates a Workload Identity Pool that trusts `https://gcp.nullify.ai`, with an `attribute_condition` that pins access to your Nullify tenant id (`assertion.tenant_id == "<your tenant id>"`). A Nullify tenant lambda signs an OIDC token and passes it to Google STS, which fetches `https://gcp.nullify.ai/jwks.json` to verify the signature before returning a federated, tenant-scoped credential.

### GitLab

The GitLab connector wraps GitLab OAuth2 as an OIDC provider, enriching the signed claims with the user's GitLab group membership so Nullify can map the user to the right repositories during sign-in.

### Bitbucket

The Bitbucket connector wraps Bitbucket OAuth2 as an OIDC provider for use with AWS Cognito, enriching claims with the user's workspace IDs, names, and slugs, plus repository full names. Repository and workspace lookups are best-effort: failures are logged but do not block sign-in.

## Setup

OIDC trust is provisioned by Nullify as part of onboarding your tenant. For cloud federation (GCP), you will create a Workload Identity Pool that trusts the Nullify issuer; your Nullify representative provides the exact pool and attribute-condition configuration for your tenant. For sign-in bridges, no customer-side OIDC configuration is required — the connectors are wired into the Nullify authentication flow automatically.

For related cloud-context setup, see the [AWS](/connectors/aws), [GCP](/connectors/gcp), and version-control connector guides.


# Nullify API

The Nullify REST API is available to enterprise tenants for automation, reporting, and connector use cases. Each section below documents a functional area of the platform.

All requests require a service-account token sent via the `Authorization: Bearer <token>` header. See [Authentication](/api-reference/api-reference/authentication) for details.

For the full machine-readable contract, download the [OpenAPI specification](/api-reference/api-reference/openapi-spec).

## Key Endpoints

| Domain                      | Endpoint                                                  | Purpose                                                                                  |
| --------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| Code Review                 | `GET /sast/events`                                        | Stream new findings, suppressions, and fixes for every repository.                       |
| Code Review                 | `GET /sast/findings`                                      | List open findings with rich filters for ownership, severity, and status.                |
| Code Review                 | `POST /sast/findings/{findingId}/autofix/fix`             | Queue an automated remediation pull request for supported languages.                     |
| Code Review                 | `POST /sast/findings/{findingId}/allowlist`               | Record a governed exception while keeping audit history intact.                          |
| Dependency Analysis         | `GET /sca/dependencies/findings`                          | Fetch dependency findings with reachability and policy metadata.                         |
| Dependency Analysis         | `POST /sca/dependencies/findings/{findingId}/autofix/fix` | Generate upgrade branches or pull requests with updated manifests and lockfiles.         |
| Dependency Analysis         | `GET /sca/containers/findings`                            | Review container image issues across registries and environments.                        |
| Secrets & Data Guardrails   | `GET /secrets/findings`                                   | Enumerate secret, credential, and sensitive-data alerts by repository or service.        |
| Secrets & Data Guardrails   | `POST /secrets/findings/{findingId}/allowlist`            | Suppress a finding once the credential is rotated or data remediated.                    |
| Pentests                    | `POST /dast/pentest/start`                                | Launch a new autonomous pentest for an onboarded application.                            |
| Pentests                    | `GET /dast/pentest/scans/{scanId}/findings`               | Retrieve findings, reproduction steps, and ownership mapping for a pentest.              |
| Bug Hunts                   | `GET /dast/bughunt/findings`                              | Fetch external attack-surface findings from bug hunts.                                   |
| Supply Chain Posture        | `GET /scpm/findings`                                      | List supply-chain posture findings (typosquats, unpinned deps, CI/CD hardening).         |
| Cloud Audits                | `GET /cspm/findings`                                      | Pull cloud misconfiguration findings across AWS, GCP, Azure, and Kubernetes.             |
| Cloud Audits                | `GET /context/applications`                               | Pull the catalog of audited services with their cloud posture classification and owners. |
| Security Program Management | `POST /manager/campaigns`                                 | Launch or update campaigns that coordinate remediation across teams and connectors.      |
| Admin                       | `GET /admin/repositories`                                 | Sync the repository catalogue, default policies, and ownership assignments.              |
| Admin                       | `GET /admin/teams`                                        | Pull team definitions and CODEOWNERS mappings for asynchronous routing.                  |

## Security Program Management

Security Program Management does not yet have a dedicated reference page, but its endpoints are available under the `/manager` namespace and documented in full in the [OpenAPI specification](/api-reference/api-reference/openapi-spec). The main endpoint groups are:

| Area                  | Example endpoint                     | Purpose                                                                        |
| --------------------- | ------------------------------------ | ------------------------------------------------------------------------------ |
| Campaigns             | `GET`, `POST /manager/campaigns`     | List, create, and update campaigns; inspect a campaign's findings and events.  |
| Action items          | `GET /manager/action-items`          | List the items Nullify has raised for human input, and reopen or resolve them. |
| Escalations           | `GET /manager/escalations`           | List and manage current escalations.                                           |
| Threat investigations | `GET /manager/threat-investigations` | Track disclosed threats, affected repositories, and remediation progress.      |
| Strategy              | `GET /manager/strategy`              | The rolling quarterly security strategy, with version history.                 |
| Chat                  | `GET /manager/chat/sessions`         | List and inspect chat sessions with Nullify.                                   |

Each dedicated page in this section includes usage examples, and the [OpenAPI specification](/api-reference/api-reference/openapi-spec) provides the full contract for automation.


# Authentication

Authenticate with the Nullify REST API to access endpoints

## Service Accounts

Create service-account tokens for any automation that talks to the Nullify API.

1. Open your tenant at `https://app.<TENANT>.nullify.ai`.
2. Navigate to **Configure → Service Accounts** (`/dashboard/configure/service-accounts/`).
3. Generate a service account, copy the token, and store it in your secrets manager. Tokens are only shown once in the UI.

Service accounts inherit the permissions you assign when creating them. Use separate accounts for different automations so you can rotate or revoke access without broad impact.

## Request Headers

Include the token in the `Authorization` header of every API call:

```bash
curl --request GET \
  --url "https://api.<TENANT>.nullify.ai/endpoint" \
  --header "Authorization: Bearer <TOKEN>"
```

A missing or malformed token returns `401 Unauthorized`. A valid token that lacks permission for the resource returns `403 Forbidden`.

## Rotation & Revocation

* Rotate service-account tokens on a schedule that matches your internal security policy.
* Remove unused accounts from **Configure → Service Accounts** to immediately revoke access.
* Audit usage in your infrastructure by monitoring for calls to `https://api.<TENANT>.nullify.ai` with the relevant account ID.


# Pagination

Nullify’s API uses cursor-based pagination for endpoints that can return large result sets (findings, events, inventories). Each response includes a `nextToken` when more data is available.

## Parameters

* `limit` – Maximum number of items returned per request. Defaults to the service-specific page size.
* `nextToken` – Cursor from the previous response. Omit this parameter on the first request.

When `nextToken` is empty or absent from the response, you have reached the end of the collection.

## Example

The example below pages through the general secret findings namespace (`/secrets/findings`). The Secrets service also exposes `/secrets/credentials/findings` (live credentials) and `/secrets/sensitivedata/findings` (sensitive data / PII), which paginate the same way.

```bash
# Request 1
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/secrets/findings?githubOwnerId=1234&limit=1'

# Response 1 (trimmed)
{
  "findings": [
    {
      "id": "01J6EEXK3NKYKWW9XTPQYAF41N"
    }
  ],
  "numItems": 1,
  "nextToken": "eyJpZCI..."
}

# Request 2
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/secrets/findings?githubOwnerId=1234&limit=1&nextToken=eyJpZCI...'

# Response 2 (trimmed)
{
  "findings": [
    {
      "id": "01J6EFVQN34BCEAHDJTE4F9Y22"
    }
  ],
  "numItems": 1,
  "nextToken": ""
}
```


# Admin

General admin API endpoints for Nullify.

## Overview

The Admin API exposes tenant-wide inventory so you can keep internal systems aligned with Nullify’s view of repositories, services, and teams. Responses mirror what security leaders see in the console and are optimised for asynchronous sync jobs rather than interactive queries.

## Base URL

All endpoints share the base URL `https://api.<TENANT>.nullify.ai`. Replace `<TENANT>` with your tenant slug (for example `https://api.acme.nullify.ai`). Include a service-account token in every request; see [Authentication](/api-reference/api-reference/authentication).

## Get Repositories

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/admin/repositories" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

{% code overflow="wrap" %}

```bash
curl -L \
   -H "Accept: application/json" \
   -H "Authorization: Bearer <TOKEN>" \
   https://api.<TENANT>.nullify.ai/admin/repositories?githubOwnerId=1234
```

{% endcode %}

## Get Teams

Returns team definitions harvested from GitHub or Bitbucket. GitHub tenants also include CODEOWNERS membership so you can align ownership data across tools.

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/admin/teams" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

{% code overflow="wrap" %}

```bash
curl -L \
   -H "Accept: application/json" \
   -H "Authorization: Bearer <TOKEN>" \
   https://api.<TENANT>.nullify.ai/admin/teams?githubOwnerId=1234
```

{% endcode %}

## Service Accounts

Service accounts issue the tokens that authenticate API automation (see [Authentication](/api-reference/api-reference/authentication)). Manage them programmatically:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/admin/service\_accounts" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/admin/service\_accounts" method="post" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/admin/service\_accounts" method="delete" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

{% code overflow="wrap" %}

```bash
# List service accounts
curl -L \
   -H "Accept: application/json" \
   -H "Authorization: Bearer <TOKEN>" \
   https://api.<TENANT>.nullify.ai/admin/service_accounts?githubOwnerId=1234

# Create a service account
curl -L -X POST \
   -H "Accept: application/json" \
   -H "Authorization: Bearer <TOKEN>" \
   -H "Content-Type: application/json" \
   -d '{"name": "ci-automation", "platform": "GitHub"}' \
   https://api.<TENANT>.nullify.ai/admin/service_accounts?githubOwnerId=1234

# Delete a service account (serviceAccountId identifies which one)
curl -L -X DELETE \
   -H "Accept: application/json" \
   -H "Authorization: Bearer <TOKEN>" \
   "https://api.<TENANT>.nullify.ai/admin/service_accounts?githubOwnerId=1234&serviceAccountId=<ID>"
```

{% endcode %}

## Integration Configuration

The Admin API also manages connector configuration. These mirror the dashboard's **Configure** screens:

* Jira: `GET`, `POST /admin/integrations/jira`
* Slack: `GET /admin/integrations/slack`, `POST /admin/integrations/slack/key`
* Microsoft Teams: `GET /admin/integrations/teams`
* Notifications (targets + event filters): `PUT /admin/integrations/notifications`
* Cloud accounts: `/admin/integrations/cloud/{aws,azure,gcp}/settings`, `/admin/integrations/cloud/k8s/whitelist`
* PR gate (fail-build, PR reviews): `PUT /admin/pr-gate-settings`
* Ticketing thresholds and SLA policies: `POST /admin/ticketing/settings/{findingType}`, `POST /admin/sla`


# Code Review API

Query code review findings and events via the Nullify API

> Existing endpoints retain the `/sast` prefix for backwards compatibility. New wrappers will be introduced over time without breaking the current contract.

## Base URL

All endpoints share the base URL: `https://api.<TENANT>.nullify.ai`. Replace `<TENANT>` with your tenant slug (for example `https://api.acme.nullify.ai`).

## Events

Receive a stream of notable code-review activity (new findings, fixes, suppressions, branch summaries):

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/sast/events" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/sast/events?githubOwnerId=1234'
```

## List Findings

List active findings with filtering by repository, severity, or status:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/sast/findings" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/sast/findings?githubOwnerId=1234&severity=high'
```

## Get a Finding

Retrieve full context for a single finding, including reachability evidence and ownership metadata:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/sast/findings/{findingId}" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/sast/findings/01J6EEXK3NKYKWW9XTPQYAF41N?githubOwnerId=1234'
```

## Update a Finding

Override a finding's priority or severity. The request accepts `priorityOverride` (`URGENT`, `IMPORTANT`, `MEDIUM`, `LOW`) and `severityOverride` (`CRITICAL`, `HIGH`, `MEDIUM`, `LOW`):

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/sast/findings/{findingId}" method="patch" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s -X PATCH \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"severityOverride": "HIGH"}' \
  'https://api.<TENANT>.nullify.ai/sast/findings/01J6EEXK3NKYKWW9XTPQYAF41N?githubOwnerId=1234'
```

## Get Triage Details

Retrieve the AI triage reasoning and reachability assessment for a finding:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/sast/findings/{findingId}/triage" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/sast/findings/01J6EEXK3NKYKWW9XTPQYAF41N/triage?githubOwnerId=1234'
```

## Allowlist a Finding

Apply a policy exception when you accept the risk for a finding. Supply an `allowlistReason` and an `allowlistType` (`AI`, `UserFixed`, `UserAssumeRisk`, `UserFalsePositive`, or `UserOther`):

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/sast/findings/{findingId}/allowlist" method="post" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s -X POST \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"allowlistReason": "Risk accepted for legacy service", "allowlistType": "UserAssumeRisk"}' \
  'https://api.<TENANT>.nullify.ai/sast/findings/01J6EEXK3NKYKWW9XTPQYAF41N/allowlist?githubOwnerId=1234'
```

## Remove a Finding from the Allowlist

Re-activate alerting for a previously allowlisted finding:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/sast/findings/{findingId}/unallowlist" method="post" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s -X POST \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/sast/findings/01J6EEXK3NKYKWW9XTPQYAF41N/unallowlist?githubOwnerId=1234'
```

## Allowlist Findings in Bulk

Allowlist multiple findings in a single call. The body adds a `findingIds` array alongside the standard `allowlistReason` and `allowlistType`:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/sast/findings/allowlist" method="post" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s -X POST \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
        "findingIds": ["01J6EEXK3NKYKWW9XTPQYAF41N", "01J6EFVQN34BCEAHDJTE4F9Y22"],
        "allowlistReason": "Risk accepted for legacy service",
        "allowlistType": "UserAssumeRisk"
      }' \
  'https://api.<TENANT>.nullify.ai/sast/findings/allowlist?githubOwnerId=1234'
```

## Trigger Autofix

Request a remediation patch for supported languages:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/sast/findings/{findingId}/autofix/fix" method="post" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s -X POST \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/sast/findings/01J6EEXK3NKYKWW9XTPQYAF41N/autofix/fix?githubOwnerId=1234'
```

## Finding Events

Review every decision applied to a finding (status changes, tickets, suppressions):

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/sast/findings/{findingId}/events" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/sast/findings/01J6EEXK3NKYKWW9XTPQYAF41N/events?githubOwnerId=1234'
```

## List Repositories

Enumerate the repositories Code Review is scanning, along with their scan status:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/sast/repositories" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/sast/repositories?githubOwnerId=1234'
```


# Dependency Analysis API

Query dependency analysis events and findings via the Nullify API

> Current endpoints use both the `/sca` and `/context/deps` prefixes. The `/sca` endpoints cover detailed findings and remediation, while the `/context/deps` endpoints cover dependency exposure and source history.

## Base URL

`https://api.<TENANT>.nullify.ai`

## List Active Dependencies

List org wide active package dependencies (paginated), available under the `/context/deps` namespace:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/context/deps/active" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/context/deps/active?githubOwnerId=1234'
```

## List Dependency History

List org wide dependency occurrence windows (paginated):

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/context/deps" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/context/deps?githubOwnerId=1234'
```

## Get package exposure

List projects with dependency exposure (active and historical) matching the ecosystem/package & semver range query:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/context/deps/exposure" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/context/deps/exposure?githubOwnerId=123&ecosystem=npm&name=react&range=>=0.0.0'
```

## Events

Track dependency alerts, suppressions, and auto-remediation updates:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/sca/events" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/sca/events?githubOwnerId=1234'
```

## List Findings

Fetch the current dependency findings (including reachability and policy status):

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/sca/dependencies/findings" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/sca/dependencies/findings?githubOwnerId=1234'
```

## Get a Finding

Retrieve detailed metadata, including recommended upgrades and reachability reasoning:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/sca/dependencies/findings/{findingId}" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/sca/dependencies/findings/01J6EEXK3NKYKWW9XTPQYAF41N?githubOwnerId=1234'
```

## Update a Finding

Override a dependency finding's priority or severity. The request accepts `priorityOverride` (`URGENT`, `IMPORTANT`, `MEDIUM`, `LOW`) and `severityOverride` (`CRITICAL`, `HIGH`, `MEDIUM`, `LOW`):

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/sca/dependencies/findings/{findingId}" method="patch" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s -X PATCH \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"severityOverride": "HIGH"}' \
  'https://api.<TENANT>.nullify.ai/sca/dependencies/findings/01J6EEXK3NKYKWW9XTPQYAF41N?githubOwnerId=1234'
```

## Get Triage Details

Fetch how Nullify triaged a finding — its severity, priority, and reasoning:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/sca/dependencies/findings/{findingId}/triage" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/sca/dependencies/findings/01J6EEXK3NKYKWW9XTPQYAF41N/triage?githubOwnerId=1234'
```

## Allowlist a Finding

Pause alerting when you accept the risk for a dependency finding. Supply an `allowlistReason` and an `allowlistType` (`AI`, `UserFixed`, `UserAssumeRisk`, `UserFalsePositive`, or `UserOther`):

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/sca/dependencies/findings/{findingId}/allowlist" method="post" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s -X POST \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"allowlistReason": "Risk accepted for legacy service", "allowlistType": "UserAssumeRisk"}' \
  'https://api.<TENANT>.nullify.ai/sca/dependencies/findings/01J6EEXK3NKYKWW9XTPQYAF41N/allowlist?githubOwnerId=1234'
```

## Remove a Finding from the Allowlist

Reverse an allowlist decision and return the finding to the active backlog:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/sca/dependencies/findings/{findingId}/unallowlist" method="post" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s -X POST \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/sca/dependencies/findings/01J6EEXK3NKYKWW9XTPQYAF41N/unallowlist?githubOwnerId=1234'
```

## Manage a Finding's Ticket

Create or remove the ticket tracking a dependency finding:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/sca/dependencies/findings/{findingId}/ticket" method="post" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s -X POST \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/sca/dependencies/findings/01J6EEXK3NKYKWW9XTPQYAF41N/ticket?githubOwnerId=1234'
```

## Trigger Autofix

Generate an upgrade pull request with lockfile updates and changelog context:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/sca/dependencies/findings/{findingId}/autofix/fix" method="post" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s -X POST \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/sca/dependencies/findings/01J6EEXK3NKYKWW9XTPQYAF41N/autofix/fix?githubOwnerId=1234'
```

## Container Findings

Dependency Analysis also evaluates container images. Use the container endpoints to fetch and triage those findings, and the SBOM endpoint to pull a repository's software bill of materials:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/sca/containers/findings" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/sca/containers/findings?githubOwnerId=1234'
```

Allowlist a container finding with the same `allowlistReason` + `allowlistType` body:

```bash
curl -s -X POST \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"allowlistReason": "Base image patched upstream", "allowlistType": "UserFixed"}' \
  'https://api.<TENANT>.nullify.ai/sca/containers/findings/01J6EEXK3NKYKWW9XTPQYAF41N/allowlist?githubOwnerId=1234'
```

Fetch the SBOM for a repository:

```bash
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/sca/repositories/01J6EEXK3NKYKWW9XTPQYAF41N/sbom?githubOwnerId=1234'
```

## List Repositories

Enumerate the repositories Dependency Analysis is reviewing, with their scan status:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/sca/repositories" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/sca/repositories?githubOwnerId=1234'
```

## Finding Events

Retrieve the decision history for a specific finding:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/sca/dependencies/findings/{findingId}/events" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/sca/dependencies/findings/01J6EEXK3NKYKWW9XTPQYAF41N/events?githubOwnerId=1234'
```


# Secrets & Data API

Query secrets, credential, and sensitive-data findings via the Nullify API

Secret and sensitive-data findings are available under two namespaces, each with the same shape:

* `/secrets/credentials/*` — detected secrets and credentials. `/secrets/findings/*` is a legacy alias for this namespace and returns the same data.
* `/secrets/sensitivedata/*` — sensitive data / PII.

Every namespace supports `GET .../findings`, `GET` and `PATCH .../findings/{findingId}`, `GET .../findings/{findingId}/triage`, `POST .../findings/{findingId}/allowlist`, `POST .../findings/{findingId}/unallowlist`, and the bulk `POST .../findings/allowlist/batch`. The examples below use `/secrets/findings`; swap the prefix for `/secrets/credentials` or `/secrets/sensitivedata` to target the other namespaces.

## Base URL

`https://api.<TENANT>.nullify.ai`

## Events

Stream events for new secrets, suppressions, and rotations:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/secrets/events" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/secrets/events?githubOwnerId=1234'
```

## List Findings

Enumerate active findings for a repository or tenant. Available in all three namespaces:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/secrets/findings" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
# Secret findings
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/secrets/findings?githubOwnerId=1234'

# Live credentials
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/secrets/credentials/findings?githubOwnerId=1234'

# Sensitive data / PII
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/secrets/sensitivedata/findings?githubOwnerId=1234'
```

## Get a Finding

Fetch full details, including detector type, remediation guidance, and history:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/secrets/findings/{findingId}" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/secrets/findings/01J6EEXK3NKYKWW9XTPQYAF41N?githubOwnerId=1234'
```

## Update a Finding

Override a finding's priority or severity, or attach notes. The request accepts `priorityOverride` (`URGENT`, `IMPORTANT`, `MEDIUM`, `LOW`), `severityOverride` (`CRITICAL`, `HIGH`, `MEDIUM`, `LOW`), and `userNotes`. Available in all namespaces:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/secrets/findings/{findingId}" method="patch" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s -X PATCH \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"severityOverride": "HIGH"}' \
  'https://api.<TENANT>.nullify.ai/secrets/findings/01J6EEXK3NKYKWW9XTPQYAF41N?githubOwnerId=1234'
```

## Get Triage Details

Fetch how Nullify triaged a finding — its severity, priority, and reasoning. Available in all namespaces:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/secrets/findings/{findingId}/triage" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/secrets/findings/01J6EEXK3NKYKWW9XTPQYAF41N/triage?githubOwnerId=1234'
```

## Allowlist a Finding

Suppress an individual finding with an audit trail. Supply an `allowlistReason` and an `allowlistType` (`AI`, `UserFixed`, `UserAssumeRisk`, `UserFalsePositive`, or `UserOther`):

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/secrets/findings/{findingId}/allowlist" method="post" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s -X POST \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"allowlistReason": "Credential rotated", "allowlistType": "UserFixed"}' \
  'https://api.<TENANT>.nullify.ai/secrets/findings/01J6EEXK3NKYKWW9XTPQYAF41N/allowlist?githubOwnerId=1234'
```

## Allowlist Findings in Bulk

Allowlist multiple findings in a single call. The body adds a `findingIds` array alongside the standard `allowlistReason` and `allowlistType`. Available in all three namespaces:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/secrets/findings/allowlist/batch" method="post" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s -X POST \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
        "findingIds": ["01J6EEXK3NKYKWW9XTPQYAF41N", "01J6EFVQN34BCEAHDJTE4F9Y22"],
        "allowlistReason": "Credentials rotated",
        "allowlistType": "UserFixed"
      }' \
  'https://api.<TENANT>.nullify.ai/secrets/credentials/findings/allowlist/batch?githubOwnerId=1234'
```

## Remove a Finding from the Allowlist

Reverse an allowlist decision and return the finding to the active backlog. Available in all namespaces:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/secrets/findings/{findingId}/unallowlist" method="post" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s -X POST \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/secrets/findings/01J6EEXK3NKYKWW9XTPQYAF41N/unallowlist?githubOwnerId=1234'
```

## Finding Events

Retrieve the decision history for a specific finding:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/secrets/findings/{findingId}/events" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/secrets/findings/01J6EEXK3NKYKWW9XTPQYAF41N/events?githubOwnerId=1234'
```


# Pentests API

Manage hands-off pentester engagements via the Nullify API

> Endpoints live under the `/dast/pentest` namespace.

## Base URL

`https://api.<TENANT>.nullify.ai`

## List Applications

Pentests run against an **application** you have onboarded. List your applications (and their `applicationId`s) before starting a run:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/dast/pentest/applications" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/dast/pentest/applications?githubOwnerId=1234'
```

Fetch a single application with `GET /dast/pentest/applications/{applicationId}`.

## List Engagements

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/dast/pentest/scans" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/dast/pentest/scans?githubOwnerId=1234'
```

## Start an Engagement

Pentests run against an application you have already onboarded. Supply the `applicationId` (from **List Applications** above) and an optional `configOverrides` object to tune the run:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/dast/pentest/start" method="post" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s -X POST \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
        "applicationId": "01J6EEXK3N6A1Q4JH1JHT0TK5X",
        "configOverrides": {}
      }' \
  'https://api.<TENANT>.nullify.ai/dast/pentest/start?githubOwnerId=1234'
```

## Retrieve Engagement Status

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/dast/pentest/scans/{scanId}" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/dast/pentest/scans/01J6EEXK3NKYKWW9XTPQYAF41N?githubOwnerId=1234'
```

## List Engagement Findings

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/dast/pentest/scans/{scanId}/findings" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/dast/pentest/scans/01J6EEXK3NKYKWW9XTPQYAF41N/findings?githubOwnerId=1234'
```

## Stop an Engagement

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/dast/pentest/scans/{scanId}/stop" method="post" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s -X POST \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/dast/pentest/scans/01J6EEXK3NKYKWW9XTPQYAF41N/stop?githubOwnerId=1234'
```

## Download an Engagement Report

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/dast/pentest/scans/{scanId}/report" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/dast/pentest/scans/01J6EEXK3NKYKWW9XTPQYAF41N/report?githubOwnerId=1234'
```

## Get a Finding

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/dast/pentest/findings/{findingId}" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/dast/pentest/findings/01J6EEXK3NKYKWW9XTPQYAF41N?githubOwnerId=1234'
```

## Triage, Allowlist & Ticket a Finding

Individual pentest findings support the same triage, allowlisting, ticketing, and event history as the other detection APIs:

* `GET /dast/pentest/findings/{findingId}/triage` — how Nullify triaged the finding.
* `POST /dast/pentest/findings/{findingId}/allowlist` and `POST /dast/pentest/findings/{findingId}/unallowlist` — suppress or restore a finding (body: `allowlistReason` + `allowlistType`).
* `POST /dast/pentest/findings/{findingId}/ticket` — open a tracking ticket.
* `GET /dast/pentest/findings/{findingId}/events` — the finding's decision history.

```bash
curl -s -X POST \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"allowlistReason": "Accepted risk on staging target", "allowlistType": "UserAssumeRisk"}' \
  'https://api.<TENANT>.nullify.ai/dast/pentest/findings/01J6EEXK3NKYKWW9XTPQYAF41N/allowlist?githubOwnerId=1234'
```

## Bug Hunt

A complementary, lighter-weight Bug Hunt workflow surfaces findings without a full pentest. SSR HTML apps and SPAs are evaluated by Bug Hunt (beta) rather than the standard pentest workflow.

* `GET /dast/bughunt/scans`
* `POST /dast/bughunt/scans` — intensity levels are `low`, `medium`, or `high`
* `GET /dast/bughunt/findings`


# Cloud Audits API

Query cloud audit posture findings and asset inventories via the Nullify API

> Posture findings live under the `/cspm` namespace. Asset-inventory and organizational-context lookups live under the `/context` namespace.

Cloud Audits (CSPM) covers AWS, GCP, Azure, and Kubernetes. Auditing is automatic — Nullify audits whenever a connected account's inventory refreshes, rather than on a fixed schedule.

## Base URL

`https://api.<TENANT>.nullify.ai`

## List Findings

Retrieve cloud misconfiguration findings across your connected AWS, GCP, Azure, and Kubernetes accounts:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/cspm/findings" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/cspm/findings?githubOwnerId=1234'
```

## Get a Finding

Pull full detail for a single posture finding, including affected resource and remediation guidance:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/cspm/findings/{findingId}" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/cspm/findings/01J6EEXK3NKYKWW9XTPQYAF41N?githubOwnerId=1234'
```

## Create a Ticket for a Finding

Open a tracking ticket for a posture finding:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/cspm/findings/{findingId}/ticket" method="post" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s -X POST \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/cspm/findings/01J6EEXK3NKYKWW9XTPQYAF41N/ticket?githubOwnerId=1234'
```

## List Scans

Review the cloud scans Nullify has executed for your accounts:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/cspm/scans" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/cspm/scans?githubOwnerId=1234'
```

## Get a Scan

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/cspm/scans/{scanId}" method="get" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/cspm/scans/01J6EEXK3NKYKWW9XTPQYAF41N?githubOwnerId=1234'
```

## Autofix

Where a misconfiguration maps to an infrastructure-as-code change, Nullify can generate a fix and open a pull request against the code that provisioned the resource. Trigger a fix, then preview the cached diff before a PR is opened:

{% openapi src="/files/1NgvLK12PRq7RFdn97yd" path="/cspm/findings/{findingId}/autofix/fix" method="post" %}
[nullify-openapi-bundle.yaml](https://3946391995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FId2OtLCL6O6V3XSRYxtr%2Fuploads%2Fgit-blob-b2518eed5de56f970ea2dc094c986d143a9ad062%2Fnullify-openapi-bundle.yaml?alt=media)
{% endopenapi %}

```bash
# Trigger a fix (opens an IaC pull request)
curl -s -X POST \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/cspm/findings/01J6EEXK3NKYKWW9XTPQYAF41N/autofix/fix?githubOwnerId=1234'

# Preview the cached fix diff
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/cspm/findings/01J6EEXK3NKYKWW9XTPQYAF41N/autofix/cache/diff?githubOwnerId=1234'
```

## Asset Inventory

Asset-inventory and organizational-context lookups live under the `/context` namespace. Use these to retrieve the application catalog and the service graph Nullify uses to route cloud audit findings to the right owners:

```bash
# Application catalog
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/context/applications?githubOwnerId=1234'

# Organizational context / service graph
curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer <TOKEN>" \
  'https://api.<TENANT>.nullify.ai/context/organization?githubOwnerId=1234'
```

The context service also exposes `/context/applications/{applicationId}`, `/context/repositories`, `/context/repositories/{repositoryId}`, and `/context/applications/rebuild` for managing the asset inventory.

Cloud Audits enrich these datasets with misconfiguration evidence before handing work to Security Program Management campaigns and autofix.


# OpenAPI Specification

Download the full Nullify OpenAPI specification:

{% file src="/files/1NgvLK12PRq7RFdn97yd" %}


# CLI

The `nullify` CLI brings the Nullify platform to your terminal and CI pipelines. Use it to authenticate, check your security posture, query and remediate findings, gate CI, and expose Nullify as an [MCP](/cli/cli/mcp-setup) server for AI assistants.

## In this section

* [Installation](/cli/cli/install) — install the binary and configure your host.
* [Authentication](/cli/cli/authentication) — log in and manage credentials.
* [MCP Setup](/cli/cli/mcp-setup) — run the CLI as an MCP server and wire it into Cursor, Codex, or Claude.

## Quick start

```bash
# Install (macOS/Linux) and point the CLI at your tenant
curl -sSfL https://raw.githubusercontent.com/Nullify-Platform/cli/main/install.sh | sh -s -- --host api.<TENANT>.nullify.ai

# One-time setup wizard (login + host + defaults)
nullify init

# See your overall posture
nullify status

# Query findings across every scanner (or scope with --type)
nullify findings --type sast
```

## Common commands

| Command                                 | What it does                                                                                 |
| --------------------------------------- | -------------------------------------------------------------------------------------------- |
| `nullify init`                          | One-time setup wizard: authenticate, set your host, and configure defaults.                  |
| `nullify status`                        | Security-posture overview across every finding type.                                         |
| `nullify findings [--type ...]`         | List findings; filter by `--severity`, `--status`, `--type`, `--repo`, `--limit`.            |
| `nullify fix <finding-id>`              | Generate an autofix for a finding, optionally opening a PR with `--create-pr`.               |
| `nullify pentest ...`                   | Run a pentest against your API (see [Pentests](/capabilities/pentests/engagement-workflow)). |
| `nullify ci gate` / `nullify ci report` | Fail a build past a threshold, or produce a markdown summary for a PR comment.               |
| `nullify repos`                         | List the repositories Nullify monitors.                                                      |
| `nullify whoami`                        | Show who you're authenticated as.                                                            |
| `nullify auth ...`                      | Log in, check status, switch hosts, and manage credentials.                                  |
| `nullify mcp serve`                     | Run the CLI as an [MCP server](/cli/cli/mcp-setup) for AI assistants.                        |

Advanced, low-level access to individual service APIs lives under `nullify api ...` (for example `nullify api sast list-findings`); most users should prefer the unified commands above.

See [Installation](/cli/cli/install) for host resolution and platform notes, and [Authentication](/cli/cli/authentication) for login details.


# Installation

Install the Nullify CLI to manage your security posture from the command line

## Quick Install

Run the install script to download the latest Nullify CLI binary:

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

Replace `<TENANT>` with your organization name (e.g. `api.acme.nullify.ai`).

{% hint style="info" %}
The `curl | sh` installer runs on macOS and Linux. It does not run on native Windows — use [WSL](https://learn.microsoft.com/windows/wsl/) or Git Bash, or follow the [Manual Installation](#manual-installation) steps with the Windows archive below.
{% endhint %}

This will:

* Detect your OS and architecture (macOS, Linux; amd64, arm64)
* Download the latest release from [GitHub Releases](https://github.com/Nullify-Platform/cli/releases)
* Verify the SHA256 checksum
* Install the binary to `/usr/local/bin/nullify` (or `~/.local/bin/`)
* Configure the CLI to connect to your Nullify instance

## Manual Installation

Download the latest archive for your platform from [GitHub Releases](https://github.com/Nullify-Platform/cli/releases):

| Platform | Architecture          | Archive                       |
| -------- | --------------------- | ----------------------------- |
| macOS    | Apple Silicon (arm64) | `nullify_darwin_arm64.tar.gz` |
| macOS    | Intel (amd64)         | `nullify_darwin_amd64.tar.gz` |
| Linux    | amd64                 | `nullify_linux_amd64.tar.gz`  |
| Linux    | arm64                 | `nullify_linux_arm64.tar.gz`  |
| Windows  | amd64                 | `nullify_windows_amd64.zip`   |

After downloading:

```bash
tar -xzf nullify_<os>_<arch>.tar.gz
sudo mv nullify /usr/local/bin/nullify
```

### With Go

If you have a Go toolchain installed, you can build and install the CLI directly:

```bash
go install github.com/nullify-platform/cli/cmd/cli@latest
```

## Verify Installation

```bash
nullify --version
```

## Configuration

The CLI resolves which Nullify instance to connect to in this order:

1. The `--host` flag
2. The `NULLIFY_HOST` environment variable
3. The host saved in `~/.nullify/config.json` by `nullify auth login`

```bash
# 1. Set via flag (highest priority)
nullify --host api.<TENANT>.nullify.ai <command>

# 2. Or set via environment variable
export NULLIFY_HOST=api.<TENANT>.nullify.ai
nullify <command>
```

`NULLIFY_HOST` (and `--host`) accept either the full API host `api.<TENANT>.nullify.ai` or the bare `<TENANT>.nullify.ai` form. The CLI stores the host in its bare `<TENANT>.nullify.ai` form and adds the `api.` prefix automatically when it makes API requests, so either form works.

## Next Steps

* [Authentication Guide](/cli/cli/authentication) - Log in to your Nullify instance
* [MCP Setup Guide](/cli/cli/mcp-setup) - Connect your AI coding assistant


# Authentication

Authenticate with the Nullify CLI using browser-based login or API tokens

## Interactive Login

For interactive usage, the CLI opens your browser to authenticate directly with your identity provider:

```bash
nullify auth login --host api.<TENANT>.nullify.ai
```

This will:

1. Open your browser to authenticate with your identity provider (GitHub, Azure, Okta, etc.)
2. Redirect back to the CLI automatically once authenticated
3. Store the host in `~/.nullify/config.json` and the tokens in `~/.nullify/credentials.json`

## Auth Commands

### Check login status

```bash
nullify auth status
```

### View current token

```bash
nullify auth token
```

### Switch between hosts

```bash
nullify auth switch --host api.<OTHER-TENANT>.nullify.ai
```

### Log out

```bash
nullify auth logout
```

### View configuration

```bash
nullify auth config
```

## CI/CD Usage

For non-interactive environments (CI/CD pipelines, scripts), use token-based authentication:

### Using a Nullify API Token

```bash
nullify --nullify-token <TOKEN> findings --type sast
```

Or set via environment variable:

```bash
export NULLIFY_TOKEN=<TOKEN>
nullify findings --type sast
```

### Using a GitHub Token

Inside a GitHub Actions workflow, pass the workflow's GitHub token with `--github-token`. The CLI exchanges it for a Nullify token:

```bash
nullify --github-token ${{ secrets.GITHUB_TOKEN }} findings --type sast
```

{% hint style="info" %}
The GitHub token exchange only triggers when the CLI is running inside GitHub Actions — specifically when `GITHUB_ACTIONS=true` **and** `GITHUB_REPOSITORY` is set (both are provided automatically by the Actions runner). The token value itself must be supplied via the `--github-token` flag. The CLI does not independently read a bare `GITHUB_TOKEN` environment variable as a credential source.
{% endhint %}

## Token Precedence

The CLI resolves authentication in this order:

1. `--nullify-token` flag
2. `NULLIFY_TOKEN` environment variable
3. `--github-token` flag, exchanged for a Nullify token — only when `GITHUB_ACTIONS=true` and `GITHUB_REPOSITORY` is set
4. Stored credentials from `nullify auth login`

## Credential Storage

`nullify auth login` writes two files under `~/.nullify/`:

* **`config.json`** — stores the host you logged in to (`{"host": "..."}`), used to resolve the default Nullify instance for subsequent commands.
* **`credentials.json`** — a per-host map of credentials (keyed by host), each entry containing `access_token`, `refresh_token`, `expires_at`, and any `query_parameters` returned at login. Written with file permissions `0600` (owner read/write only).

Tokens are automatically refreshed using the stored refresh token when they expire.


# MCP Setup

Connect your AI coding assistant to Nullify using the Model Context Protocol (MCP)

The Nullify CLI includes a built-in MCP server that gives AI coding assistants access to your security findings, triage tools, and more.

## Prerequisites

* [Install the Nullify CLI](/cli/cli/install)
* [Authenticate with your Nullify instance](/cli/cli/authentication)

## Claude Code

Add Nullify as an MCP server:

```bash
claude mcp add nullify -- nullify mcp serve
```

The Nullify tools will be automatically available in your Claude Code sessions.

## Cursor

Add to your project's `.cursor/mcp.json`:

```json
{
  "mcpServers": {
    "nullify": {
      "command": "nullify",
      "args": ["mcp", "serve"]
    }
  }
}
```

To register a different set of tools, append the optional `--tools` flag (see [Tool Sets](#tool-sets)):

```json
{
  "mcpServers": {
    "nullify": {
      "command": "nullify",
      "args": ["mcp", "serve", "--tools", "all"]
    }
  }
}
```

## Codex

Add to your Codex MCP configuration:

```json
{
  "mcpServers": {
    "nullify": {
      "command": "nullify",
      "args": ["mcp", "serve"]
    }
  }
}
```

The optional `--tools` flag selects which tool set is exposed (see [Tool Sets](#tool-sets)):

```json
{
  "mcpServers": {
    "nullify": {
      "command": "nullify",
      "args": ["mcp", "serve", "--tools", "findings"]
    }
  }
}
```

## Tool Sets

The `--tools` flag controls which tools the MCP server registers. If omitted, the `default` set is used.

| Value      | Tools registered                                                                        |
| ---------- | --------------------------------------------------------------------------------------- |
| `default`  | Unified finding tools, composite workflow tools, plus Context, Manager, and Admin tools |
| `all`      | Every tool, including the scanner-specific SAST/SCA/Secrets/Pentest/Bug Hunt/CSPM tools |
| `minimal`  | Composite workflow tools only                                                           |
| `findings` | Unified finding tools plus composite workflow tools                                     |
| `admin`    | Admin, Context, Manager, and composite tools                                            |

```bash
nullify mcp serve --tools all
```

## Available Tools

The exact tools registered depend on the selected [tool set](#tool-sets). The `default` set exposes the unified, composite, Context, Manager, and Admin tools listed below. Use `--tools all` to additionally expose the per-scanner tools.

### Unified findings tools

These work across every scanner type (`sast`, `sca_dependency`, `sca_container`, `secrets`, `pentest`, `bughunt`, `cspm`); pass the `type` argument to scope to one scanner, or omit it to query across all of them.

* `nullify_search_findings` - Search findings across all or a specific scanner type (filters: severity, status, repository)
* `nullify_get_finding` - Get details of a specific finding by type and ID
* `nullify_triage_finding` - Update the triage status of a finding
* `nullify_create_ticket` - Create a ticket for a finding
* `nullify_get_finding_events` - Get the event history for a finding
* `nullify_fix_finding` - Generate an autofix and optionally open a PR

### Composite workflow tools

* `get_security_posture_summary` - High-level counts by severity across every finding type
* `get_findings_for_repo` - All findings for a specific repository across every scanner
* `get_critical_path` - Critical and high severity findings across all types
* `get_security_trends` - How the security posture has changed over time
* `remediate_finding` - Orchestrate generate-autofix → diff → create-PR for a finding

### Context tools

* `list_repositories` - List repositories monitored by Nullify
* `get_repository` - Get details of a specific monitored repository
* `list_applications` - List applications (logical groupings of repositories and services)
* `get_application` - Get details of a specific application
* `list_dependencies` - List third-party dependencies across monitored repositories
* `get_sbom` - Get the SBOM for a repository project
* `get_dependency_exposure` - Get exposure analysis for a dependency

### Manager tools

* `list_campaigns` - List remediation campaigns
* `get_campaign` - Get details of a specific campaign
* `list_escalations` - List current escalations

### Admin tools

* `get_metrics_overview` - High-level finding counts by severity and type
* `get_metrics_over_time` - Security metrics trends over a time period
* `get_global_config` - The organization's global Nullify configuration
* `list_teams` - List teams in the organization
* `list_sla_policies` - List SLA policies
* `get_organization` - Get organization details

### Scanner-specific tools (`--tools all`)

These per-scanner tools are registered only when the `all` tool set is selected. They mirror the unified tools above but are bound to a single scanner. Examples:

* SAST: `list_sast_findings`, `get_sast_finding`, `list_sast_repositories`, `triage_sast_finding`, `generate_sast_autofix`, `get_sast_autofix_diff`, `create_sast_autofix_pr`, `create_sast_ticket`, `get_sast_finding_events`
* SCA: `list_sca_dependency_findings`, `get_sca_dependency_finding`, `list_sca_container_findings`, `get_sca_container_finding`, `triage_sca_dependency_finding`, `triage_sca_container_finding`, `generate_sca_autofix`, `get_sca_autofix_diff`, `create_sca_autofix_pr`, `create_sca_ticket`, `get_sca_finding_events`
* Secrets: `list_secrets_findings`, `get_secrets_finding`, `triage_secrets_finding`, `create_secrets_ticket`, `get_secrets_finding_events`
* Pentest: `list_pentest_findings`, `get_pentest_finding`, `list_pentest_scans`, `get_pentest_scan`, `start_pentest`, `stop_pentest_scan`, `get_pentest_report`, `triage_pentest_finding`, `create_pentest_ticket`, `get_pentest_finding_events`
* Bug Hunt: `list_bughunt_findings`
* CSPM: `list_cspm_findings`, `get_cspm_finding`, `list_cspm_scans`

## Verifying the Connection

After configuring your AI assistant, you can verify the connection by asking:

> "List my critical SAST findings"

With the `default` tool set the assistant uses `nullify_search_findings` (with `type: sast`) and returns results from your Nullify instance.

## Troubleshooting

### "Not authenticated" error

Run `nullify auth status` to check your login status. If not authenticated, run `nullify auth login`.

### Tools not appearing

Ensure the `nullify` binary is in your PATH. Run `which nullify` to verify.

### Connection issues

Check that your host is correctly configured with `nullify auth config`.


# General Configuration

## Overview

Once installed, Nullify immediately starts assessing pull requests and creating findings for new risks. The defaults get most teams moving quickly, and you tailor behaviour from the Nullify dashboard (with an equivalent admin API for automation). Configuration no longer lives in a checked-in file — every setting is managed centrally per tenant.

## Where settings live

| Setting                                       | Dashboard                     | API                                                  |
| --------------------------------------------- | ----------------------------- | ---------------------------------------------------- |
| CI / PR gate (fail build, enable PR reviews)  | **Configure → Pull Requests** | `PUT /admin/pr-gate-settings`                        |
| Ticket creation thresholds (per finding type) | **Configure → Ticketing**     | `POST /admin/ticketing/settings/{findingType}`       |
| SLA policies                                  | **Configure → Ticketing**     | `POST /admin/sla`                                    |
| Notification destinations + event filters     | **Configure → Connectors**    | `PUT /admin/integrations/notifications`              |
| Ignore / allowlist a finding                  | Per-finding in the dashboard  | `POST /{service}/.../findings/{findingId}/allowlist` |

### Pull request gate

Control whether the Nullify check can fail CI when a pull request introduces a new finding, the priority threshold that fails it, and whether Nullify may submit an approving review. These live on **Configure → Pull Requests** (`PUT /admin/pr-gate-settings`).

`PUT /admin/pr-gate-settings` fields:

* `enabled` — fail the Nullify check when the PR introduces a finding at or above the threshold.
* `min_priority` — the lowest priority that fails the check. Accepts `URGENT` or `IMPORTANT` only.
* `allow_approving_reviews` — allow Nullify to submit an approving review on a pull request that has no blocking findings (off by default).
* `allow_request_changes_reviews` — allow Nullify to submit a request-changes review when it has a blocking security concern (off by default).

See [Pull Request Configuration](/configuration/configuration-pull-requests) for the full behaviour of each setting.

### Ticketing thresholds and SLAs

Ticket auto-creation is **off by default** for every finding type until you enable it per type from **Configure → Ticketing** (`POST /admin/ticketing/settings/{findingType}`). Once enabled, a ticket is created when a finding meets both the configured severity and priority thresholds.

Default thresholds once enabled:

* Priority default: **IMPORTANT** (and Nullify never auto-creates tickets below IMPORTANT, regardless of the threshold).
* Severity default: no floor.

SLA policies (time-to-remediate targets per priority) are set with `POST /admin/sla`. Valid priority labels are `URGENT`, `IMPORTANT`, `MEDIUM`, `INFORMATIONAL`, `LOW`, `NEGLIGIBLE`, `FALSE_POSITIVE`, and `UNKNOWN`.

### Notifications

Notification destinations and the events that trigger them are managed from **Configure → Connectors** (`PUT /admin/integrations/notifications`). Nullify supports three destination types, each pairable with an event filter:

* **Slack** and **Microsoft Teams** — post escalations and updates to a channel or DM.
* **Email** — deliver notifications to an address.
* **Outbound webhook** — POST notification events to an HTTPS endpoint you control.

### Allowlisting findings

There is no file-based or static ignore list. Suppress a finding per-finding from the dashboard or via the service allowlist endpoint, e.g. `POST /sast/findings/{findingId}/allowlist`. See the per-service configuration pages for the exact paths.

## Autofix pull request limits

Autofix volume is fixed platform behaviour and is not configurable:

* **3** open Nullify pull requests per repository at a time.
* **5** new pull requests per remediation plan.

Issue dashboards are generated automatically from finding counts; there is no toggle to enable them.

## Document ingestion (Memory Vault)

Nullify automatically ingests organisational documents committed to your platform's private configuration repository into the Memory Vault. Depending on your platform this repository is:

* GitHub: `.github-private`
* Bitbucket: `.nullify`
* Azure DevOps: `.azure-private`

The repository should remain private and accessible to the Nullify app. On every push, eligible files are uploaded to the vault and fed to the onboarding agent to build organisational context (security posture, escalation matrices, compliance controls) that informs vulnerability prioritisation.

Eligible file types:

* Markdown (`.md`)
* PDF (`.pdf`)
* Plain text (`.txt`)
* Structured data: JSON (`.json`), CSV (`.csv`)
* Images: PNG, JPG/JPEG, GIF, SVG, WEBP, BMP, TIFF

Skipped at the repository root (to avoid boilerplate noise): `README.md`, `package.json`, `package-lock.json`, `tsconfig.json`. The same filenames inside subdirectories are ingested normally.

Useful examples to commit: security architecture diagrams, on-call escalation matrices, GDPR/SOC 2 attestations, vendor risk assessments, incident response runbooks, access review exports. Files can also be uploaded interactively from the onboarding UI — both paths feed the same vault.


# Pull Request Configuration

## Overview

The **Configure → Pull Requests** page controls how Nullify's code-review check behaves on each pull request: whether it can fail CI, what priority of finding is severe enough to fail it, and whether Nullify is allowed to submit an approving review.

These settings are tenant-wide — they apply to every connected repository — and require the **Admin** role to change. They map directly to the admin API (`PUT /admin/pr-gate-settings`), so the same behaviour can be automated.

## The Nullify check

Nullify reports a single check named **Nullify** on every pull request, covering code (SAST), dependency (SCA), and secrets analysis together. By default this check is **informational** — it reports findings but always concludes successfully, so it never blocks a merge. The settings below let you turn it into a required gate.

## Settings

### Fail CI check on findings

When **enabled**, the Nullify check reports a **failed** status when the pull request introduces at least one finding at or above the chosen [minimum priority](#minimum-priority). When **disabled** (the default), the check always passes and only surfaces findings as comments and dashboard entries.

The gate evaluates **net-new findings only** — issues this pull request introduces relative to its target branch. Pre-existing findings in the backlog never fail an unrelated pull request, so enabling the gate does not retroactively block in-flight work.

{% hint style="info" %}
Failing the check does not block the merge on its own. To turn it into a hard merge requirement, mark the **Nullify** check as required in your provider's branch-protection / merge-check rules (for example, GitHub branch protection's "Require status checks to pass").
{% endhint %}

### Minimum priority

Sets the lowest finding priority that fails the check when the gate is enabled:

* **Urgent only** — only `URGENT` findings fail the check.
* **Important and above** — both `URGENT` and `IMPORTANT` findings fail the check.

Lower-priority findings (`MEDIUM` and below) are always reported but never fail the check.

### Allow approving reviews

This setting governs only whether Nullify is allowed to **approve** a pull request. It does not change the other review verdicts.

When Nullify has a concrete security concern about code the pull request introduced or changed, it can post a review:

* **Request changes** — for critical / high-severity issues that should be fixed before merge (see **Allow request-changes reviews** below).
* **Comment** — for a security concern that should be raised but is not severe enough to block.

A pull request with no security concern receives no review comment at all; the passing **Nullify** check is the all-clear signal.

The **Allow approving reviews** toggle controls only the clean-PR case:

* **Off (default)** — Nullify never submits an approving review. A clean pull request is left without a review (the passing check is the signal).
* **On** — Nullify may submit an **approving** review on a pull request that has no blocking findings. Because a bot approval can satisfy a required-reviewer rule, this is opt-in.

{% hint style="warning" %}
Enabling approving reviews means a Nullify approval can count toward a required-reviewer rule on your branch protection. Leave this off if you want every pull request to still require a human approver.
{% endhint %}

### Allow request-changes reviews

This setting controls whether Nullify may submit a **request-changes** review when it has a blocking security concern about the pull request.

* **Off (default)** — Nullify raises blocking concerns as **comments** rather than a request-changes review.
* **On** — Nullify submits a **request-changes** review for critical / high-severity concerns. Because a request-changes review can block a merge under branch protection, this is opt-in.

This toggle is independent of the approving-reviews setting and of the CI check gate.

{% hint style="info" %}
The review verdict and the **Nullify** check are independent. The check's pass/fail comes only from the net-new finding gate ([Fail CI check on findings](#fail-ci-check-on-findings) + [Minimum priority](#minimum-priority)); a *request changes* review does not by itself fail the check, and a failing check does not depend on the review verdict.
{% endhint %}

## Equivalent API

| Setting                       | API field                       | Values                |
| ----------------------------- | ------------------------------- | --------------------- |
| Fail CI check on findings     | `enabled`                       | `true` / `false`      |
| Minimum priority              | `min_priority`                  | `URGENT`, `IMPORTANT` |
| Allow approving reviews       | `allow_approving_reviews`       | `true` / `false`      |
| Allow request-changes reviews | `allow_request_changes_reviews` | `true` / `false`      |

```http
PUT /admin/pr-gate-settings
{
  "enabled": true,
  "minPriority": "IMPORTANT",
  "allowApprovingReviews": false,
  "allowRequestChangesReviews": false
}
```

All settings default to off / `URGENT` for tenants that have never saved the page.


# Code Configuration

## Overview

Code findings (SAST) are configured from the Nullify dashboard — there is no checked-in config file. Query findings with `GET /sast/findings` (and `/detailed`, `/preview`); fetch a single finding with `GET /sast/findings/{findingId}`, and update one with `PATCH /sast/findings/{findingId}`.

## Autofix

Nullify drafts pull requests automatically for supported languages. Autofix is enabled per tenant; its pull request volume is fixed platform behaviour and not configurable:

* **3** open Nullify pull requests per repository at a time.
* **5** new pull requests per remediation plan.

## Pull request gate

Whether Nullify reviews pull requests and whether a code finding can fail CI is controlled from **Configure → Pull Requests** (`PUT /admin/pr-gate-settings`): `enabled`, `min_priority` (`URGENT` or `IMPORTANT`), `allow_approving_reviews`, and `allow_request_changes_reviews`. See [Pull Request Configuration](/configuration/configuration-pull-requests) for details.

## Ignore findings

Accept a risk by allowlisting the specific finding — either per-finding from the dashboard or via the API:

```
POST /sast/findings/{findingId}/allowlist
```

Request body:

```json
{
  "allowlistReason": "HTTP requests with variables in tests are acceptable",
  "allowlistType": "UserAssumeRisk"
}
```

`allowlistType` is one of `AI`, `UserFixed`, `UserAssumeRisk`, `UserFalsePositive`, or `UserOther`. To allowlist several findings at once, use `POST /sast/findings/allowlist` and add a `findingIds` array to the same body. Remove an allowlist entry with `POST /sast/findings/{findingId}/unallowlist`.

Allowlist events also flow through the [Nullify API](/api-reference/api-reference) so you can export them for compliance, metrics, or SIEM correlation.


# Dependency Configuration

## Overview

Software Composition Analysis (SCA) covers both **dependencies** and **container** findings. Dependency findings are configured from the Nullify dashboard — there is no checked-in config file. Query them with `GET /sca/dependencies/findings` (and `/detailed`, `/preview`); fetch or update a single finding with `GET,PATCH /sca/dependencies/findings/{findingId}`.

## Autofix

Nullify drafts dependency upgrade pull requests automatically. Autofix is enabled per tenant; its pull request volume is fixed platform behaviour and not configurable:

* **3** open Nullify pull requests per repository at a time.
* **5** new pull requests per remediation plan.

## Pull request gate

Whether a dependency finding can fail CI is controlled from **Configure → Pull Requests** (`PUT /admin/pr-gate-settings`). See [Configuration – General](/configuration/configuration-general).

## Ignore findings

Suppress a known advisory or defer remediation by allowlisting the specific finding — per-finding from the dashboard or via the API:

```
POST /sca/dependencies/findings/{findingId}/allowlist
```

Request body:

```json
{
  "allowlistReason": "Library only used in test harnesses",
  "allowlistType": "UserAssumeRisk"
}
```

`allowlistType` is one of `AI`, `UserFixed`, `UserAssumeRisk`, `UserFalsePositive`, or `UserOther`.

Use campaigns to track deferred work and prevent forgotten exceptions. Container hardening findings are configured separately — see [Configuration – Containers](/configuration/configuration-containers).


# Container Configuration

## Overview

Container hardening findings are produced by Software Composition Analysis (SCA) and configured from the Nullify dashboard — there is no checked-in config file. Query findings with `GET /sca/containers/findings`; fetch a single finding with `GET /sca/containers/findings/{findingId}`, and update one with `PATCH /sca/containers/findings/{findingId}`.

## SBOM

Retrieve the software bill of materials for a repository's container images:

```
GET /sca/repositories/{repositoryId}/sbom
```

## Ignore findings

Suppress a container finding by allowlisting it per-finding from the dashboard or via the API:

```
POST /sca/containers/findings/{findingId}/allowlist
```

Request body:

```json
{
  "allowlistReason": "Base image patched in next release",
  "allowlistType": "UserAssumeRisk"
}
```

`allowlistType` is one of `AI`, `UserFixed`, `UserAssumeRisk`, `UserFalsePositive`, or `UserOther`. Dependency findings are configured separately — see [Configuration – Dependencies](/configuration/configuration-dependencies).


# Secrets Configuration

## Overview

Secrets detection is configured from the Nullify dashboard — there is no checked-in config file. Findings are split across two namespaces:

* `/secrets/credentials/*` — detected secrets and credentials (API keys, tokens, and other secrets). `/secrets/findings/*` is a legacy alias for this same namespace and returns the same data.
* `/secrets/sensitivedata/*` — sensitive data such as PII.

Each namespace exposes the same shape: `GET .../findings`, `GET,PATCH .../findings/{findingId}`, `POST .../findings/{findingId}/allowlist`, and a bulk `POST .../findings/allowlist/batch`.

## Ignore secrets

Allowlist a secret when you have rotated it or verified it is safe to retain. Suppression is per-finding from the dashboard or via the API, e.g.:

```
POST /secrets/findings/{findingId}/allowlist
```

Request body:

```json
{
  "allowlistReason": "Rotated and stored in the secure vault",
  "allowlistType": "UserFixed"
}
```

`allowlistType` is one of `AI`, `UserFixed`, `UserAssumeRisk`, `UserFalsePositive`, or `UserOther`. To allowlist several findings at once, use the batch endpoint for the relevant namespace (e.g. `POST /secrets/findings/allowlist/batch`) and add a `findingIds` array to the body.


# API Configuration

## Overview

Dynamic Application Security Testing (DAST) produces API findings by testing your running applications. Manage applications, targets, and credentials from the Nullify dashboard or through the API — there is no config file checked into your repository.

A pentest runs against an application's resolved context. Start one with `POST /dast/pentest/start` (body `{"applicationId":"<id>","configOverrides":{...}}`; `applicationId` is required) and review results with `GET /dast/pentest/scans/{scanId}/findings`. Lighter bug-hunt scans run at `low`, `medium`, or `high` intensity via `GET,POST /dast/bughunt/scans`.

When you run a pentest with the [CLI](/capabilities/pentests/engagement-workflow#cli), you can supply multi-role authentication as a local file with `--auth-config`. That file is a CLI input, not a checked-in platform config — the dashboard remains the source of truth for stored applications and credentials.

## Service account tokens

CLI and API access outside a browser session uses a service-account token. Manage service accounts through the admin API — `GET`, `POST`, and `DELETE` on `/admin/service_accounts` — which requires the **Admin** role. Creation accepts one built-in role: `builtin:viewer`, `builtin:editor`, or `builtin:admin`. It does not accept individual permission grants; omitting `role` defaults to `builtin:admin`.

Choose the least-privileged built-in role that covers the task:

| Task                                                       | Minimum role     |
| ---------------------------------------------------------- | ---------------- |
| Start or stop a scan, allowlist a finding, create a ticket | `builtin:editor` |
| Read scans, findings, and reports                          | `builtin:viewer` |
| Create or update applications and credentials              | `builtin:admin`  |
| Read stored credentials                                    | `builtin:admin`  |

Pass the token as `NULLIFY_TOKEN` in the environment, or with `--nullify-token`.

## Credentials

Credentials are stored per tenant, encrypted at rest, and returned masked. Manage them with:

```
GET    /dast/credentials
POST   /dast/credentials
PUT    /dast/credentials/{credentialId}
DELETE /dast/credentials/{credentialId}
POST   /dast/credentials/{credentialId}/validate
```

Validate a credential before using it in an engagement. A pentest stops at the login stage if any attached credential fails to authenticate.

## Source IP addresses

Pentest and exploit-validation traffic originates from a static set of IPs for your tenant's region. Retrieve them and allowlist them on any WAF or gateway in front of your targets:

```
GET /dast/sourceips
```

## Ignore findings

Suppress an API finding by allowlisting it per-finding from the dashboard or via the API, e.g. for a pentest finding:

```
POST /dast/pentest/findings/{findingId}/allowlist
```

Request body:

```json
{
  "allowlistReason": "Endpoint is internal-only and not reachable in production",
  "allowlistType": "UserAssumeRisk"
}
```

`allowlistType` is one of `AI`, `UserFixed`, `UserAssumeRisk`, `UserFalsePositive`, or `UserOther`.

## Pull request gate and notifications

Whether findings fail CI and where alerts are delivered is managed centrally — see [Configuration – General](/configuration/configuration-general) for the **Configure → Pull Requests** and **Configure → Notifications** settings.


# GitHub Install

Nullify is currently available as a GitHub App that can be installed across select repositories or all current and future repositories, allowing for organization-wide coverage in one click.

If you wish to create your own self-hosted GitHub app, please refer to these [instructions](/enterprise-tier/github-self-hosted-app).

Please reach out to our team to assist with your org setup.

## Install Nullify

1. Please click the link that Nullify provided to you separately along with this documentation and follow the instructions to install the app.
2. In **Install App**, install the App in your organization:

<figure><img src="/files/FACApZjyeRSykRQ3BUhJ" alt=""><figcaption><p>Install App</p></figcaption></figure>

3. In **Repository access**, select the repositories you would like to install Nullify:

<figure><img src="/files/HtpBPbZGQAaJqY2QyG3s" alt=""><figcaption><p>Repository access</p></figcaption></figure>


# Self-Hosted GitHub App

Nullify is currently available as a GitHub App that can be installed across select repositories or all current and future repositories, allowing for organization-wide coverage in one click.

Please reach out to our team to assist with your org setup.

## Install Nullify

### Setting up your GitHub application

See [creating a GitHub App](https://docs.github.com/apps/building-github-apps/creating-a-github-app/) for general guidance on setting up GitHub Apps.

1. Go to `https://github.com/organizations/<insert-your-org-name>/settings/apps/new`
2. **GitHub App Name:** Enter the name of your application.
3. **Homepage URL:** Enter `https://nullify.ai`

<div align="left"><figure><img src="/files/Wd6vngXopGJNO7cCWpi8" alt=""><figcaption><p>GitHub App name</p></figcaption></figure> <figure><img src="/files/J28PyVNKNjhWMpQR0Nke" alt=""><figcaption><p>Homepage URL</p></figcaption></figure> <figure><img src="/files/so5mBcJJrBstno2YAWYA" alt=""><figcaption><p>Webhook URL</p></figcaption></figure></div>

4. Add the **Callback URL** (provided to you by Nullify)
5. Enable **Request user authorization (OAuth) during installation**
6. Enable **Device Flow**

**Webhook:**

7. Add the **Webhook URL**. It is of the form `https://api.<your-organization>.nullify.ai/core/github/webhook`.
8. Add the **Webhook secret** (a shared secret you generate and also send to Nullify)
9. Enable **SSL verification**

**Display Information:**

10. Add the Nullify logo, available from the [brand kit](/more-info/brand-kit)

<figure><img src="/files/CrudHJCRvtCvEFnlOJpq" alt=""><figcaption><p>Nullify logo added to GitHub App display information</p></figcaption></figure>

11. Grant access to the following **Repository** **permissions**:

| **Permission**                                   | **Access**   | **Explanation**                                                                     |
| ------------------------------------------------ | ------------ | ----------------------------------------------------------------------------------- |
| Actions                                          | Read-only    | Read required for the autofix workflow to fix failing CI jobs                       |
| Checks                                           | Read & write | Write required to create and modify Check Runs                                      |
| Contents                                         | Read & write | Write required for the autofix engine to read code and commit fixes to fix branches |
| Issues                                           | Read & write | Write required to create and modify Issues                                          |
| <p><strong>GitHub.com:</strong> Metadata<br></p> | Read-only    | This setting is automatically set by GitHub                                         |
| Commit statuses                                  | Read-only    | Required to receive CI build status events from any provider                        |
| Pull Requests                                    | Read & write | Write required to create and modify Pull Requests                                   |

12. Grant access to the following **Organization** **permissions**:

| **Permission** | **Access** | **Explanation** |
| -------------- | ---------- | --------------- |
| Members        | Read       | Read teams      |

13. Subscribe to the following **Events**:

| **Event Type**              | **Subscription** |
| --------------------------- | ---------------- |
| Pull Request                | Required         |
| Push                        | Required         |
| Check Run                   | Required         |
| Check suite                 | Required         |
| Issues                      | Required         |
| Issue Comment               | Required         |
| Pull Request Review         | Required         |
| Pull Request Review Comment | Required         |
| Repository                  | Required         |
| Status                      | Required         |

14. In the "Where can this GitHub App be installed?" section, select **Only on this account**. Then select **Create GitHub App**.

<figure><img src="/files/6xVx2nLRuofH3aDpaMyO" alt=""><figcaption></figcaption></figure>

15. Navigate to the **Developer settings** of your new App. In **General**, get the App ID and Client ID.

<div align="left"><figure><img src="/files/pxEHxRZkbF8exjWjYXiu" alt=""><figcaption><p>GitHub App - Developer settings</p></figcaption></figure> <figure><img src="/files/Oanxr2U36TIwejD5JMsK" alt=""><figcaption><p>App ID &#x26; Client ID</p></figcaption></figure></div>

16. In **General**, generate a new Client secret.
17. In **General**, generate a private key:

<figure><img src="/files/uZvN9pwFMqP30IUjwFZ0" alt=""><figcaption><p>Private key</p></figcaption></figure>

18. Send the App ID, Client ID, Client Secret and Private Key to Nullify. This can be done via a password manager.
19. Wait for your stack to be provisioned.
20. In **Install App**, install the App in your organization:

<figure><img src="/files/KkPMsfRucGRs0Va8EOvt" alt=""><figcaption><p>Install App</p></figcaption></figure>

21. In **Repository access**, select the repositories you would like to install Nullify:

<figure><img src="/files/HtpBPbZGQAaJqY2QyG3s" alt=""><figcaption><p>Repository access</p></figcaption></figure>


# GitLab Install

Nullify integrates with GitLab.com (SaaS) and self-managed GitLab. Connecting Nullify requires two things: an access token so Nullify can call the GitLab API, and a webhook so GitLab streams repository events to Nullify.

Please reach out to our team to assist with your group setup.

## Install Nullify

### 1. Create an access token

Nullify uses a GitLab access token to resolve the group that owns each project, read diffs, and post merge-request reviews. Create either a **group access token** (recommended, so coverage applies to every project in the group) or a personal access token for a service account.

1. In GitLab, navigate to your group's **Settings → Access tokens** (or **Edit profile → Access tokens** for a personal access token).
2. Create a token with the **`api`** scope and the **Maintainer** role.
3. Provide the token to your Nullify representative. Nullify stores it encrypted and uses it for all GitLab API calls.

{% hint style="info" %}
For self-managed GitLab, also share your instance base URL (e.g. `https://gitlab.your-company.com`) so Nullify targets the correct API host.
{% endhint %}

### 2. Add the webhook

Nullify ingests GitLab events at a single endpoint. Add a webhook so GitLab delivers events to it. A **group webhook** covers every project in the group; a project webhook covers one project.

1. Navigate to **Settings → Webhooks** for your group (or project).
2. Set the **URL** to your Nullify webhook endpoint:

   ```
   https://api.<your-organization>.nullify.ai/core/gitlab/webhook
   ```

   Your Nullify representative will confirm the exact host for your stack.
3. Set the **Secret token** to the value provided by your Nullify representative. GitLab sends this in the `X-Gitlab-Token` header and Nullify verifies it on every delivery.
4. Under **Trigger**, enable the following events:
   * **Push events**
   * **Comments**
   * **Merge request events**
   * **Pipeline events**
5. Keep **SSL verification** enabled and click **Add webhook**.

{% hint style="info" %}
Nullify acknowledges deliveries it does not act on (for example pushes with no new commits) with a success response, so unrelated webhook deliveries will still show as delivered in GitLab.
{% endhint %}

### 3. Final steps

6. Wait for your stack to be provisioned by the Nullify team.
7. Once provisioned, you'll receive confirmation and can begin using Nullify to assess your GitLab projects. Open a merge request or push to a branch to trigger the first scan.

## Support

If you encounter any issues during the installation process, please contact our support team for assistance.


# Bitbucket Install

Nullify is available as a Bitbucket App that can be installed across select repositories or all current and future repositories, allowing for workspace-wide coverage in one click.

Please reach out to our team to assist with your workspace setup.

Nullify is distributed as a Bitbucket Connect App. You install it from a descriptor URL provided by Nullify; Bitbucket then calls back to Nullify automatically to complete the installation and register the required webhooks (`repo:push`, `pullrequest:created`, `pullrequest:updated`, `pullrequest:comment_created`). There is no OAuth consumer to create and no Key/Secret to share.

## Install Nullify

### Setting up your Bitbucket App

1. Navigate to your Bitbucket workspace settings and go to **Apps and features**.

<figure><img src="/files/D5QRFpXVHRsHx5raTOxi" alt="Bitbucket Apps and features page"><figcaption><p>Apps and features page</p></figcaption></figure>

2. Click on **Installed apps** in the left sidebar.
3. Enable **Development mode** by ticking the checkbox.
4. Click on **Install app from URL**.

<figure><img src="/files/bhAI9okLb1Lz5UGzC25Q" alt="Install app from URL button"><figcaption><p>Install app from URL</p></figcaption></figure>

5. Paste in the Nullify descriptor URL provided by your Nullify representative. It is of the form `https://api.<your-organization>.nullify.ai/core/bitbucket/descriptor`.

<figure><img src="/files/SF6Th8IPmJf74fSbB6gf" alt="Install app from URL modal"><figcaption><p>Install app from URL modal</p></figcaption></figure>

6. Review the requested scopes (`account`, `repository`, `pullrequest`) and click **Install** to approve.
7. Bitbucket calls Nullify's install endpoint automatically, which registers the app for your workspace and sets up the webhooks. No manual webhook creation is required.

### Final Steps

8. Wait for your stack to be provisioned by the Nullify team.
9. Once provisioned, you'll receive confirmation and can begin using Nullify to assess your Bitbucket repositories.

## Support

If you encounter any issues during the installation process, please contact our support team for assistance.


# Azure DevOps Install

## Creating the Azure App

1. **Sign in to Azure Portal:**
   * Go to [Azure Portal](https://portal.azure.com/).
   * Sign in with your Azure account credentials.
2. **Register a New Application:**
   * In the Azure Portal, select **App registrations**.
   * Click on the **+ New registration** button at the top of the App registrations page.

<figure><img src="/files/bDaEMbSLcL8OILsBmjVe" alt=""><figcaption><p>New App Registration</p></figcaption></figure>

3. **Enter Application Details:**
   * **Name:** Enter a name for your application ("Nullify").
   * **Supported Account Types:** Choose the option for who can use the application:
     * Accounts in this organizational directory only (single tenant)
4. **Register the Application:**
   * Click **Register** to create the application. This action will register the application with Azure and generate a unique Application (client) ID and Directory (tenant) ID.
   * Record the `client_id` and `tenant_id`.

<figure><img src="/files/e3voHd79XRdwv4WBF7bC" alt=""><figcaption><p>Client ID and Tenant ID</p></figcaption></figure>

6. **Create Client Secret:**

   * In the left-hand menu of your app's registration page, select **Certificates & secrets**.
   * Under **Client secrets**, click on **New client secret**.

   <figure><img src="/files/zAmv6bgk3K8qSZQSNK8v" alt=""><figcaption><p>New client secret</p></figcaption></figure>

   * Provide a description for the secret (e.g., "Nullify App") and select an expiration period (e.g., 6 months, 12 months, 24 months).
   * Click **Add**.
   * After creating the client secret, it will be displayed only once. Make sure to copy and securely store this value immediately. This is your `client_secret`. Make sure you copied the secret value and not the secret id.

   <figure><img src="/files/8G4giE0rw5P3wYyXsVKS" alt=""><figcaption><p>Client Secret Value</p></figcaption></figure>
7. **Configure API Permissions:**

   * Nullify authenticates to Azure DevOps with the **client credentials** flow, using the Client ID, Client Secret, and Tenant ID you provide below. It requests the Azure DevOps `.default` scope, which grants access to the Azure DevOps API surface.
   * Go to **API Permissions** in the left menu.
   * Click **+ Add a permission** and select **APIs my organization uses**.
   * Search for and select **Azure DevOps**.
   * Add the `user_impersonation` permission, then click **Add permissions**.

   <figure><img src="/files/cuaFkIZgBXuHo6IuHZeO" alt=""><figcaption><p>Azure DevOps Application Permission</p></figcaption></figure>

   * Grant admin consent for Default Directory

   <figure><img src="/files/XZp1qqRAYV3diXLis926" alt=""><figcaption><p>Admin Consent</p></figcaption></figure>
8. **Authentication**

   * In the left menu, click on **Authentication**.
   * Under Platform configurations click \`\`+ Add a platform\` and choose **Web** application.
   * Under Web Redirect URIs, add the redirect URI that Nullify provides for your tenant (your Nullify representative will give you the exact value).

   <figure><img src="/files/62O45aXpEPV3nAKT3bT5" alt=""><figcaption><p>Authentication Redirect URI</p></figcaption></figure>
9. **Provide Credentials to Nullify**

Please provide Nullify with the following credentials to complete your installation:

* **Client ID**
* **Client Secret**
* **Tenant ID**

## Add the Nullify App to your DevOps Organization

1. **Add Nullify as a User:**
   * Go to your Azure DevOps organization.
   * Navigate to **Organization settings**.
   * Select **Users** from the left-hand menu.
   * Click on **Add users**.
   * In the search box, type "Nullify" and select the Nullify service principal.
   * Set the **Access Level** to **Basic**.
   * Note: If you encounter an error saying "can't be added at this time," keep trying as it may be a temporary glitch.

<figure><img src="/files/eYtGUpNvhtQ9W1h22QzQ" alt=""><figcaption><p>Add Nullify (Service Principal) as a user</p></figcaption></figure>

## Configure Webhooks for Azure DevOps

The Nullify Dashboard can create all the necessary webhooks needed. You can do this across the whole organization or at the repository level. You can also create the webhooks manually.

### Automatic webhook creation

1. **Give Nullify permissions to create webhooks** Add the Nullify Service Principal User as an Administrator to your Azure DevOps organization. (This can be temporary until after the webhooks are created.)
   * Go to the **Project Settings** of the project you want to create webhooks for.
   * Navigate to **Permissions**.
   * Add the Nullify service principal to the **Project Collection Administrators** group.

<figure><img src="/files/Fha2hLhJAU5sq1pApdZi" alt=""><figcaption><p>Add Nullify to the Project Collection Administrator group</p></figcaption></figure>

2. **Create webhooks via the Nullify dashboard**

* Log in to your Nullify dashboard (<https://app.your-company.nullify.ai>)
* Option 1: Creating webhooks across the organization.

  * Go to the **Configure** tab and under the Info menu, click on **Initialize Azure Webhooks**.

  <figure><img src="/files/DtiyjZCOKwzgpl2MSMkY" alt=""><figcaption><p>Add Nullify to the necessary permission groups</p></figcaption></figure>
* Option 2: Creating webhooks per repo

  * Go to the **Configure** tab and under the **Repositories** menu, you will see a list of repositories with the column **Is Initialized?**
  * This column indicates whether webhooks have been configured for that repository.
  * Click on the **Initialize** button to create webhooks.

  <figure><img src="/files/pt67zIaJGTl9F0O9RFYg" alt=""><figcaption><p>Repositories configuration</p></figcaption></figure>

3. You may now remove administrator privilages for Nullify from the DevOps organization.

### Manual webhook creation

1. **Create a Service Hook Subscription:**
   * Go to your Azure DevOps organization.
   * Navigate to the project where you want to configure the webhook.
   * Select **Project Settings** from the bottom left corner.
   * Under **General**, select **Service hooks**.
   * Click on **+ Create subscription**.
2. **Select the Service:**
   * In the list of services, select **Web Hooks**.
   * Click **Next**.
3. **Configure the Trigger:**
   * Choose the following events that will trigger the webhook: Code Pushed, Pull request commented on, Pull Request created, Pull Request updated, Work item updated. Azure only allows one event to be selected at a time so you will need to do steps 1-4 for each event type.
   * Click **Next**.
4. **Configure the Action:**
   * Enter the **URL** of the webhook endpoint provided by Nullify.
   * The URL is of the form `https://api.<your-organization>.nullify.ai/core/azure/webhook`
   * Basic authentication username is `nullify`.
   * Basic authentication password is the webhook shared secret provided to you by Nullify.
   * Click **Finish** to create the webhook subscription.

<figure><img src="/files/yZ1Idw8X40taELsYWHRy" alt=""><figcaption><p>Webhook creation</p></figcaption></figure>


# Grafana Data Source

The Nullify Grafana data source plugin (`nullify-grafana-datasource`) lets you visualize your Nullify scan results and trends directly in Grafana. Point the data source at the Nullify API and build dashboards for SAST, SCA, and Secrets findings and events.

{% hint style="info" %}
This plugin can only be installed on a self-hosted (local / on-prem) Grafana instance. Grafana Cloud is **not** currently supported.
{% endhint %}

## Installation

1. Install the plugin into your Grafana server instance following the [Grafana plugin management guide](https://grafana.com/docs/grafana/latest/administration/plugin-management/#install-plugin-on-local-grafana).
2. Add a new **Nullify** data source. See the [Grafana data source management guide](https://grafana.com/docs/grafana/latest/administration/data-source-management/).
3. On the Nullify data source settings page, configure the connection (see below).
4. Import a dashboard JSON to start visualizing your data. See the [Grafana import dashboards guide](https://grafana.com/docs/grafana/latest/dashboards/build-dashboards/import-dashboards/).

## Configuration

The data source settings page ("Nullify API Settings") has three fields:

| Field                    | Description                                                                                                                                                                                                                                    |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Nullify API Host URL** | The host name for the Nullify API, e.g. `https://api.YOUR_NULLIFY_ID.nullify.ai`.                                                                                                                                                              |
| **GitHub Owner ID**      | The globally unique numerical GitHub ID for your individual/organization account. This is the numeric ID, not your username or organization name. You can find it at `https://api.github.com/users/YOUR_GITHUB_USERNAME_OR_ORGANIZATION_NAME`. |
| **Nullify API Key**      | An API key for the Nullify API. Create one at `https://app.YOUR_NULLIFY_ID.nullify.ai/`. The key is stored as a secret and used only by the plugin's backend.                                                                                  |

## Query types

Each query targets one of six Nullify endpoints. Every query type supports scoping by repository (GitHub repository IDs or query strings) and by owner (team/user names or query strings), so panels can be filtered to a service, team, or repository. The **summary** queries report current finding state; the **events** queries report a time series of changes (new findings, fixes, allowlisting, and so on).

| Query type          | Endpoint          | Description                                                                                              |
| ------------------- | ----------------- | -------------------------------------------------------------------------------------------------------- |
| **SAST Summary**    | `sast/summary`    | Current SAST findings, filterable by repositories, owners, and severity.                                 |
| **SAST Events**     | `sast/events`     | Time series of SAST events, filterable by repositories, owners, branch, and event types.                 |
| **SCA Summary**     | `sca/summary`     | Current SCA findings, filterable by repositories, owners, and package.                                   |
| **SCA Events**      | `sca/events`      | Time series of SCA events, filterable by repositories, owners, branch, and event types.                  |
| **Secrets Summary** | `secrets/summary` | Current Secrets findings, filterable by repositories, owners, branch, secret type, and allowlist status. |
| **Secrets Events**  | `secrets/events`  | Time series of Secrets events, filterable by repositories, owners, branch, and event types.              |

### Event types

The events queries can be filtered to specific event types:

* **SAST events:** New Branch Summary, New Finding, New Fix, New Allowlisted Finding, New Unallowlisted Finding, New Pull Request Finding, New Pull Request Fix, New Pull Request Allowlisted Finding, New Pull Request Unallowlisted Finding.
* **SCA events:** New Branch Summary, New Finding, New Allowlisted Finding, New Fix, New Pull Request Finding, New Pull Request Fix.
* **Secrets events:** New Finding, New Allowlisted Finding.

## Template variables

The plugin ships a variable query editor with two variable types you can use to build dynamic, filterable dashboards:

* **Repository** — populate a variable with your repositories.
* **Owner** — populate a variable with owners (teams and users).


# Defender GitHub Action

The Nullify Defender GitHub Action (`nullify-platform/nullify-defender-action`) scans your dependencies for vulnerabilities, malware, and unpinned versions using Nullify Defender. It runs as a composite action that downloads the Nullify Defender CLI, scans your repository, and surfaces results as GitHub annotations, SARIF (Code Scanning), a pull-request comment, and a step summary.

## Quick start

```yaml
name: Nullify Defender Scan
on:
  pull_request:
  push:
    branches: [main]

permissions:
  contents: read
  security-events: write
  pull-requests: write

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: nullify-platform/nullify-defender-action@v1
        with:
          api-key: ${{ secrets.NULLIFY_API_KEY }}
```

## Required permissions

| Permission               | Why it is needed                           |
| ------------------------ | ------------------------------------------ |
| `contents: read`         | Check out the repository code to scan.     |
| `security-events: write` | Upload SARIF results to Code Scanning.     |
| `pull-requests: write`   | Post the summary comment on pull requests. |

## Inputs

| Name                | Required | Default                                | Description                                                                                      |
| ------------------- | -------- | -------------------------------------- | ------------------------------------------------------------------------------------------------ |
| `api-key`           | Yes      |                                        | Nullify API key.                                                                                 |
| `api-url`           | No       | `https://api.nullify.ai/vuln-database` | Nullify API base URL.                                                                            |
| `scan-mode`         | No       | `auto`                                 | Scan mode: `full`, `diff`, or `auto`. `auto` uses `diff` for pull requests and `full` otherwise. |
| `resolve`           | No       | `false`                                | Force re-resolution of dependencies before scanning.                                             |
| `policy`            | No       |                                        | Override which severities cause failures (e.g. `critical,high,malware`).                         |
| `fail-on-findings`  | No       | `true`                                 | Fail the action if any findings are detected.                                                    |
| `output-format`     | No       | `github-annotations`                   | Output format: `github-annotations`, `sarif`, or `json`.                                         |
| `working-directory` | No       | `.`                                    | Working directory for the scan.                                                                  |
| `cli-version`       | No       | `latest`                               | Version of the Nullify Defender CLI to use.                                                      |

## Outputs

| Name             | Description                             |
| ---------------- | --------------------------------------- |
| `findings-count` | Total number of findings.               |
| `critical-count` | Number of critical severity findings.   |
| `high-count`     | Number of high severity findings.       |
| `medium-count`   | Number of medium severity findings.     |
| `low-count`      | Number of low severity findings.        |
| `malware-count`  | Number of malware findings.             |
| `unpinned-count` | Number of unpinned dependency findings. |
| `sarif-file`     | Path to the generated SARIF file.       |

## Features

* **Automatic scan mode** — runs a diff scan on pull requests and a full scan on pushes when `scan-mode` is left at `auto`.
* **SARIF upload** — findings are uploaded to GitHub Code Scanning and appear in the repository's **Security** tab.
* **PR comments** — posts a summary table on pull requests, updating the existing comment on re-runs instead of stacking new ones.
* **Configurable policy** — override which severity levels cause the workflow to fail via the `policy` input.
* **Step summary** — results are written to the GitHub Actions step summary.

## Advanced usage

### Custom policy

Only fail on critical, high, and malware findings:

```yaml
- uses: nullify-platform/nullify-defender-action@v1
  with:
    api-key: ${{ secrets.NULLIFY_API_KEY }}
    policy: "critical,high,malware"
```

### Force dependency resolution

Re-resolve all dependencies before scanning:

```yaml
- uses: nullify-platform/nullify-defender-action@v1
  with:
    api-key: ${{ secrets.NULLIFY_API_KEY }}
    resolve: "true"
```

### Scan a subdirectory

```yaml
- uses: nullify-platform/nullify-defender-action@v1
  with:
    api-key: ${{ secrets.NULLIFY_API_KEY }}
    working-directory: "packages/backend"
```

### Pin the CLI version

```yaml
- uses: nullify-platform/nullify-defender-action@v1
  with:
    api-key: ${{ secrets.NULLIFY_API_KEY }}
    cli-version: "v0.5.0"
```

### Non-blocking scan

Run the scan without failing the workflow:

```yaml
- uses: nullify-platform/nullify-defender-action@v1
  with:
    api-key: ${{ secrets.NULLIFY_API_KEY }}
    fail-on-findings: "false"
```

### Use outputs in subsequent steps

```yaml
- uses: nullify-platform/nullify-defender-action@v1
  id: defender
  with:
    api-key: ${{ secrets.NULLIFY_API_KEY }}
    fail-on-findings: "false"

- name: Check results
  run: |
    echo "Total findings: ${{ steps.defender.outputs.findings-count }}"
    echo "Critical: ${{ steps.defender.outputs.critical-count }}"
    if [ "${{ steps.defender.outputs.critical-count }}" -gt 0 ]; then
      echo "::error::Critical vulnerabilities found!"
      exit 1
    fi
```


# Security

At Nullify, we take the security of our systems and our users' data very seriously. If you have discovered a security vulnerability, we appreciate your help in disclosing it to us in a responsible manner.

## Reporting a Vulnerability

If you believe you have found a security vulnerability in our platform, please follow these steps:

1. **Email us**: Send an email to <security@nullify.ai> with the details of the vulnerability. Please include:
   * A description of the vulnerability and its potential impact.
   * Steps to reproduce the vulnerability.
   * Any relevant screenshots or proof-of-concept code.
2. **Do not publicly disclose**: Please do not publicly disclose the vulnerability until we have had a chance to investigate and address it.
3. **Act in good faith**: Ensure that your research and disclosure activities are conducted in good faith and do not violate any applicable laws or disrupt our services.

## Our Commitment

When you report a vulnerability to us, we commit to:

* Providing an estimated timeline for addressing the vulnerability.
* Keeping you informed of our progress as we work to resolve the issue.
* Recognizing your contribution to improving our security, if you wish to be acknowledged.

NOTE: We may not always be able to respond to all reports for low severity issues.

## Safe Harbor

We will not take legal action against individuals who discover and report security vulnerabilities in accordance with this responsible disclosure policy. We consider activities conducted consistent with this policy to be:

* Authorized conduct under the Computer Fraud and Abuse Act (CFAA).
* Exempt from DMCA claims.
* Exempt from any applicable anti-hacking laws.

Thank you for helping us keep Nullify secure.

## Contact

For any questions or further information, please contact us at <security@nullify.ai>.


# Brand Kit

## Nullify Logo

\
![](/files/5wiQaeyi3BLr24eb73qJ)<br>

Right click to save logo


# Data Handling Policy

Nullify processes the minimum customer data needed to detect, triage, and remediate security findings. This includes source code and metadata from connected repositories, dependency and container inventories, cloud configuration retrieved through least-privilege roles, and the organizational context you provide during onboarding. Data is encrypted in transit and at rest, scoped to your tenant, and access is restricted to the systems and personnel required to operate the platform. Findings, remediation history, and supporting context are retained for the lifetime of your tenant and removed in line with the retention schedules and deletion processes described in our legal documentation. Nullify does not sell customer data or use it to train models for other customers.

## Data subject rights

Nullify supports data-subject access and erasure requests. A tenant administrator can export the personal data Nullify holds for a user, or request its erasure, through the Admin surface — see the [Admin API](/api-reference/api-reference/api-admin) or contact support to action a request.

Please see our [legal documentation here](https://www.nullify.ai/legal).

For GDPR compliance inquiries, including Records of Processing Activities, data retention schedules, breach notification procedures, and data subject rights, contact <support@nullify.ai>.


