diff --git a/api-reference/archives/request-archive-upload.mdx b/api-reference/archives/request-archive-upload.mdx new file mode 100644 index 0000000..6377d69 --- /dev/null +++ b/api-reference/archives/request-archive-upload.mdx @@ -0,0 +1,20 @@ +--- +title: "Request archive upload" +openapi: "POST /archives/uploads" +description: "Get a URL to upload a code archive to, for code that isn't in a connected repository." +--- + +Returns a signed URL for uploading a `.zip`, `.7z` or `.tar.gz` source archive. + +**Scope required**: `write`. **Role**: member or above. + +1. Call this endpoint with the archive's `file_name`. +2. `PUT` the file to `upload_url`, with the `Content-Type` header set to the returned `content_type`. +3. Add `{ "source": "upload", "archive_id": "" }` to `repos` when you [create a pentest draft](/api-reference/pentests/create-pentest). + +To assess part of the archive, set `include_paths` or `exclude_paths` on its entry in `repos`. You can use the same uploaded archive. + + + Some self-hosted deployments return `400` because they don't support API + uploads. In that case, upload the archive through the Hacktron web app. + diff --git a/api-reference/credits/get-credit-balance.mdx b/api-reference/credits/get-credit-balance.mdx new file mode 100644 index 0000000..62fd70a --- /dev/null +++ b/api-reference/credits/get-credit-balance.mdx @@ -0,0 +1,9 @@ +--- +title: "Get credit balance" +openapi: "GET /credits" +description: "Read the organization's pentest credit balance." +--- + +Returns the pentest credits available to the organization, with totals purchased, used, refunded and granted. `is_unlimited` is true for organizations without a credit limit. + +**Scope required**: `read` diff --git a/api-reference/findings/count-findings.mdx b/api-reference/findings/count-findings.mdx new file mode 100644 index 0000000..a1ebdb2 --- /dev/null +++ b/api-reference/findings/count-findings.mdx @@ -0,0 +1,9 @@ +--- +title: "Count findings" +openapi: "GET /findings/counts" +description: "Count findings by severity, state and repository." +--- + +Counts findings using the same filters as [List findings](/api-reference/findings/list-findings), without requiring you to fetch every page. The response includes the total and counts by severity, state and repository. Severity and state counts include zero values. Repositories appear in order of finding count, highest first. + +**Scope required**: `read` diff --git a/api-reference/pentests/check-pentest-logins.mdx b/api-reference/pentests/check-pentest-logins.mdx new file mode 100644 index 0000000..7417ccb --- /dev/null +++ b/api-reference/pentests/check-pentest-logins.mdx @@ -0,0 +1,17 @@ +--- +title: "Check pentest logins" +openapi: "POST /pentests/{id}/login-checks" +description: "Check that Hacktron can sign in to each login target before the pentest starts." +--- + +Checks whether Hacktron can sign in to each of the draft's `login_targets` using the saved login instructions. Every login check must pass before the pentest can start. + +**Scope required**: `write`. **Role**: member or above. + +- A successful check is valid for one hour. Start the pentest within that time or run the check again. +- Hacktron skips checks that are already running or passed within the last hour, unless the site or login instructions have changed. +- Set `wait_seconds` to wait up to 50 seconds for the checks. Without it, the endpoint returns while checks are `pending`. You can call again with `wait_seconds` to wait for the results. Each call retries failed logins, so update incorrect instructions before calling again. + +Each entry in `login_targets` includes a `login_check` status: `pending` or `running` while the check is in progress, `succeeded` after a successful sign-in, or `failed` if Hacktron couldn't sign in. + +A failed check includes a `failure_reason` without login details. Correct the instructions with [Update pentest draft](/api-reference/pentests/update-pentest), then run the check again. diff --git a/api-reference/pentests/create-pentest.mdx b/api-reference/pentests/create-pentest.mdx new file mode 100644 index 0000000..80c0f90 --- /dev/null +++ b/api-reference/pentests/create-pentest.mdx @@ -0,0 +1,31 @@ +--- +title: "Create pentest draft" +openapi: "POST /pentests" +description: "Create a pentest that hasn't started, from repositories or uploaded archives, optionally with live sites." +--- + +Creates a draft with the code you want to review and any deployed sites you want to test. Creating a draft does not run a pentest or spend credits. You review the estimate before [starting it](/api-reference/pentests/start-pentest). + +**Scope required**: `write`. **Role**: member or above. + +## Set up and start a pentest + +1. Create the draft with this endpoint, and change it with [Update pentest draft](/api-reference/pentests/update-pentest). +2. If the draft has `login_targets`, run [Check pentest logins](/api-reference/pentests/check-pentest-logins) until each login passes. +3. Get the credit cost with [Estimate pentest](/api-reference/pentests/estimate-pentest). +4. Start it with [Start pentest](/api-reference/pentests/start-pentest), or delete it with [Discard pentest draft](/api-reference/pentests/discard-pentest). + +Save the `scan_id` from the response. Use the same ID throughout setup and after the pentest starts, including when calling [Get scan](/api-reference/scans/get-scan). + +## What to test + +- `repos` (at least one): connected repositories, public repositories, or archives uploaded with [Request archive upload](/api-reference/archives/request-archive-upload). Set a repository's `include_paths` and `exclude_paths` to review only part of the code. +- `target_urls`: deployed sites to test without signing in. +- `login_targets`: deployed sites to test signed in. Each has a `site` and `login_instructions`: the login page, username, password and any extra steps. Login instructions are stored encrypted and never returned. + +A draft can have `target_urls` or `login_targets`, but not both. To test sites with and without a login, create separate pentests. + + + Signed-in testing is enabled per organization. If it isn't enabled for yours, + a request with `login_targets` returns `400`. + diff --git a/api-reference/pentests/discard-pentest.mdx b/api-reference/pentests/discard-pentest.mdx new file mode 100644 index 0000000..ff3dd85 --- /dev/null +++ b/api-reference/pentests/discard-pentest.mdx @@ -0,0 +1,9 @@ +--- +title: "Discard pentest draft" +openapi: "DELETE /pentests/{id}" +description: "Delete a pentest that hasn't started." +--- + +Deletes a draft pentest and its saved login details. To end a pentest that has already started, use [Stop scan](/api-reference/scans/stop-scan). + +**Scope required**: `write`. **Role**: member or above. diff --git a/api-reference/pentests/estimate-pentest.mdx b/api-reference/pentests/estimate-pentest.mdx new file mode 100644 index 0000000..a880967 --- /dev/null +++ b/api-reference/pentests/estimate-pentest.mdx @@ -0,0 +1,13 @@ +--- +title: "Estimate pentest" +openapi: "POST /pentests/{id}/estimate" +description: "Price a draft pentest before starting it." +--- + +Starts a cost estimation for the draft and returns its `cost_estimation_id`. Poll [Get cost estimation](/api-reference/cost-estimations/get-cost-estimation) until its `status` is `completed` or `partial`. + +**Scope required**: `write`. **Role**: member or above. + +When you [start the pentest](/api-reference/pentests/start-pentest), confirm the estimate's `total_credits`. Add `triage_fee_credits` if `triage_required` is true or you opt in to triage. + +Estimate again whenever you change the draft. diff --git a/api-reference/pentests/start-pentest.mdx b/api-reference/pentests/start-pentest.mdx new file mode 100644 index 0000000..6efa2b9 --- /dev/null +++ b/api-reference/pentests/start-pentest.mdx @@ -0,0 +1,14 @@ +--- +title: "Start pentest" +openapi: "POST /pentests/{id}/start" +description: "Start an estimated pentest and spend the confirmed credits." +--- + +Starts a draft pentest that has a completed estimate. Credits are deducted when it starts. + +**Scope required**: `write`. **Role**: organization admin. + +- Set `expected_credits` to the estimate's `total_credits`, plus `triage_fee_credits` if triage is required or you set `triage_enabled`. If the current cost differs from this total, the endpoint returns `409` without spending credits. If the organization has insufficient credits, it returns `402`. +- Every login target needs a login check that passed within the last hour. See [Check pentest logins](/api-reference/pentests/check-pentest-logins). + +After it starts, follow the pentest with [Get scan status](/api-reference/scans/get-scan-status) using the same `scan_id`, read its results with [List scan findings](/api-reference/scans/list-scan-findings), or stop it with [Stop scan](/api-reference/scans/stop-scan). diff --git a/api-reference/pentests/update-pentest.mdx b/api-reference/pentests/update-pentest.mdx new file mode 100644 index 0000000..5921350 --- /dev/null +++ b/api-reference/pentests/update-pentest.mdx @@ -0,0 +1,16 @@ +--- +title: "Update pentest draft" +openapi: "PATCH /pentests/{id}" +description: "Change a pentest that hasn't started." +--- + +Updates a draft pentest. Fields in the request replace their saved values. Omitted fields keep their current values. You can't change a pentest after it starts. + +**Scope required**: `write`. **Role**: member or above. + +- `repos` replaces the saved repository list and must contain at least one entry. Changing it clears the estimate. +- `login_targets` replaces the full list. Send `[]` to remove all login targets. Sending the same site and login instructions preserves a successful login check. Changing either resets `login_check` to `unverified` until you check again. +- To fix a login that failed its check, send the site again with new `login_instructions`, then run [Check pentest logins](/api-reference/pentests/check-pentest-logins). +- To switch a draft from signed-in testing to testing without a login (or back), clear the other list in the same request, for example `"login_targets": []` with your `target_urls`. + +Run the estimate again after changing the draft. [Start pentest](/api-reference/pentests/start-pentest) rejects a credit total that doesn't match the current estimate. diff --git a/api-reference/repos/get-pr-review-settings.mdx b/api-reference/repos/get-pr-review-settings.mdx new file mode 100644 index 0000000..471b857 --- /dev/null +++ b/api-reference/repos/get-pr-review-settings.mdx @@ -0,0 +1,16 @@ +--- +title: "Get PR review settings" +openapi: "GET /pr-review-settings" +description: "Read how PR reviews behave across your organization." +--- + +Returns the PR review settings configured for your organization under **Settings** in Hacktron: + +- `scm_comments_enabled`: reviews post comments and finding details on pull and merge requests. +- `scm_system_comments_enabled`: reviews also post system comments, such as skipped reviews, usage limits and configuration warnings. +- `fail_on_severity`: the severity at or above which a finding fails the PR check, or `null` when it never fails. +- `scan_include_*` and `scan_exclude_*`: which pull requests are reviewed, by target branch, label, author and title keyword. `null` means no filter. + +A repository's [`.hacktron/config.yaml`](/code-review/config) takes precedence over these defaults. To check whether reviews are enabled for a repository, read its `prScansEnabled` value from [List repositories](/api-reference/repos/list-repos). + +**Scope required**: `read` diff --git a/api-reference/repos/set-pr-reviews.mdx b/api-reference/repos/set-pr-reviews.mdx new file mode 100644 index 0000000..b8e7a14 --- /dev/null +++ b/api-reference/repos/set-pr-reviews.mdx @@ -0,0 +1,13 @@ +--- +title: "Turn PR reviews on or off" +openapi: "POST /repos/pr-reviews" +description: "Turn automatic pull and merge request reviews on or off for repositories." +--- + +Enables automatic pull and merge request reviews for repositories in `enable` and disables them for repositories in `disable`. Use repository IDs from [List repositories](/api-reference/repos/list-repos) for any supported provider. Each list can contain up to 100 IDs per request. + +**Scope required**: `write`. **Role**: organization admin. + +- First, enable Hacktron access to the repository from **Repositories** in Hacktron. +- If one repository fails, Hacktron continues with the others. The response reports how many changed in `processed` and lists failures with their reasons in `failedRepositories`. +- `webhookPending` lists enabled repositories whose provider webhook is still pending registration. Reviews begin once the webhook is registered. diff --git a/api-reference/repos/update-pr-review-settings.mdx b/api-reference/repos/update-pr-review-settings.mdx new file mode 100644 index 0000000..9aeb933 --- /dev/null +++ b/api-reference/repos/update-pr-review-settings.mdx @@ -0,0 +1,13 @@ +--- +title: "Update PR review settings" +openapi: "PATCH /pr-review-settings" +description: "Change how PR reviews behave across your organization." +--- + +Updates your organization's PR review settings and returns the full settings object. Only fields in the request change. Send `null` to disable `fail_on_severity` or clear a filter. + +**Scope required**: `write`. **Role**: organization admin. + +Each filter replaces the saved list. To add a branch without removing existing entries, first read [Get PR review settings](/api-reference/repos/get-pr-review-settings), then send the full list with your addition. + +Branch and author filters take glob patterns (`*`, `**`, `?`, `{a,b}`) and are case-insensitive. Each filter holds up to 50 entries. diff --git a/api-reference/scans/stop-scan.mdx b/api-reference/scans/stop-scan.mdx new file mode 100644 index 0000000..b28c2c9 --- /dev/null +++ b/api-reference/scans/stop-scan.mdx @@ -0,0 +1,9 @@ +--- +title: "Stop scan" +openapi: "POST /scans/{id}/stop" +description: "Stop a running scan or pentest." +--- + +Stops a running scan and returns its status. Members can stop their own pentests and any PR scan. Organization admins can stop any scan. + +**Scope required**: `write`. **Role**: member or above. diff --git a/code-review/integrations/slack.mdx b/code-review/integrations/slack.mdx index a3884d1..0617666 100644 --- a/code-review/integrations/slack.mdx +++ b/code-review/integrations/slack.mdx @@ -27,20 +27,18 @@ Open the finding card and click on **Mark Valid** or **Triage Comment**. ## Link your Slack identity -To perform actions on findings directly from Slack and for Hacktron to tag -you on Slack, you need to link your Slack identity to your Hacktron account -so that Hacktron can recognize you on Slack. +Link your Slack and Hacktron accounts so you can update findings from Slack +and Hacktron can mention you in notifications. - - Click on your organization name in the top left corner and go to - **Account**. + + Open **Personal settings** in Hacktron. - Scroll down to **Connected Accounts** and click on **Connect** next to + Find **Connected accounts** and click **Connect** next to **Slack**. - Connected Accounts + Connected accounts in Personal settings, with a Connect button next to Slack @@ -57,11 +55,11 @@ so that Hacktron can recognize you on Slack. - After authorization, Hacktron redirects you back to **Account**. + After authorization, Hacktron redirects you back to **Personal settings**. -You can now provide feedback and act on findings directly from Slack. +You can now provide feedback and update findings from Slack. To ask about findings, prepare a pentest or manage PR reviews, [mention @Hacktron AI](/platform/slack-assistant). - Create an organization API key — see [Authentication](/api-reference/authentication#creating-an-api-key). Grant `read` & `write` access to let your assistant triage findings. + Create an organization API key using the [Authentication guide](/api-reference/authentication#creating-an-api-key). Grant `read` and `write` access if you want your assistant to triage findings. Run in terminal: @@ -53,7 +53,7 @@ description: "Choose your platform and copy the config. No API key required." When Claude Desktop connects to Hacktron for the first time, complete the Hacktron browser sign-in. If the prompt does not appear, open Claude Desktop's MCP server settings and reconnect `hacktron`. - Create an organization API key — see [Authentication](/api-reference/authentication#creating-an-api-key). Grant `read` & `write` access to let your assistant triage findings. + Create an organization API key using the [Authentication guide](/api-reference/authentication#creating-an-api-key). Grant `read` and `write` access if you want your assistant to triage findings. ```json { @@ -91,7 +91,7 @@ description: "Choose your platform and copy the config. No API key required." When Cursor prompts you to authenticate `hacktron`, complete the Hacktron browser sign-in. You can also open Cursor's MCP settings, select `hacktron`, and start authentication from there. - Create an organization API key — see [Authentication](/api-reference/authentication#creating-an-api-key). Grant `read` & `write` access to let your assistant triage findings. + Create an organization API key using the [Authentication guide](/api-reference/authentication#creating-an-api-key). Grant `read` and `write` access if you want your assistant to triage findings. ```json { @@ -129,7 +129,7 @@ description: "Choose your platform and copy the config. No API key required." Complete the Hacktron browser sign-in. In an interactive Codex session, type `/mcp` to check the server's connection and authentication status. - Create an organization API key — see [Authentication](/api-reference/authentication#creating-an-api-key). Grant `read` & `write` access to let your assistant triage findings. + Create an organization API key using the [Authentication guide](/api-reference/authentication#creating-an-api-key). Grant `read` and `write` access if you want your assistant to triage findings. **1.** Add your API key to your shell profile (`~/.zshrc` or `~/.bashrc`): @@ -172,15 +172,52 @@ description: "Choose your platform and copy the config. No API key required." ## Tools -| Name | Description | -| --------------------- | ----------------------------------------------- | -| `list_scans` | List scans with severity breakdown | -| `get_scan` | Get details about a scan | -| `export_findings` | Export a scan's findings as JSON, CSV, or SARIF | -| `list_findings` | List findings across the organisation | -| `get_finding` | Get details about a finding | -| `update_finding` | Update a finding's state or severity | -| `add_finding_comment` | Post a comment on a finding | +Use these tools to work with Hacktron from your AI assistant. The linked [REST API endpoints](/api-reference/introduction) provide the same operations and permission requirements. + +Viewers can read scans and findings. Members can update findings and prepare pentests. Starting a pentest or changing PR review settings requires an organization admin. + +### Scans and findings + +| Name | Description | REST endpoint | +| --------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------ | +| `list_scans` | List scans with finding counts by severity | [`GET /scans`](/api-reference/scans/list-scans) | +| `get_scan` | Get details about a scan | [`GET /scans/{id}`](/api-reference/scans/get-scan) | +| `export_findings` | Export a scan's findings as JSON, CSV, or SARIF | [`GET /scans/{id}/findings/export`](/api-reference/scans/export-scan-findings) | +| `stop_scan` | Stop a running scan or pentest | [`POST /scans/{id}/stop`](/api-reference/scans/stop-scan) | +| `list_findings` | List findings across your organization | [`GET /findings`](/api-reference/findings/list-findings) | +| `count_findings` | Count findings by severity, state and repository | [`GET /findings/counts`](/api-reference/findings/count-findings) | +| `get_finding` | Get details about a finding | [`GET /findings/{id}`](/api-reference/findings/get-finding) | +| `update_finding` | Update a finding's state or severity | [`PATCH /findings/{id}`](/api-reference/findings/update-finding) | +| `add_finding_comment` | Post a comment on a finding | [`POST /findings/{id}/comments`](/api-reference/findings/add-finding-comment) | + +### Pentests + +| Name | Description | REST endpoint | +| ------------------------ | --------------------------------------------------- | ----------------------------------------------------------------------------------- | +| `list_repositories` | List connected repositories | [`GET /repos`](/api-reference/repos/list-repos) | +| `request_archive_upload` | Get a URL to upload a code archive | [`POST /archives/uploads`](/api-reference/archives/request-archive-upload) | +| `create_pentest_draft` | Create a draft with code and optional live sites | [`POST /pentests`](/api-reference/pentests/create-pentest) | +| `update_pentest_draft` | Change a pentest that hasn't started | [`PATCH /pentests/{id}`](/api-reference/pentests/update-pentest) | +| `verify_pentest_login` | Check that Hacktron can sign in to each site | [`POST /pentests/{id}/login-checks`](/api-reference/pentests/check-pentest-logins) | +| `estimate_pentest` | Estimate a draft pentest's credit cost | [`POST /pentests/{id}/estimate`](/api-reference/pentests/estimate-pentest) | +| `get_cost_estimation` | Get a pentest cost estimate | [`GET /cost-estimations/{id}`](/api-reference/cost-estimations/get-cost-estimation) | +| `get_credit_balance` | Read your organization's pentest credit balance | [`GET /credits`](/api-reference/credits/get-credit-balance) | +| `start_pentest` | Start a pentest and spend its credits (admins only) | [`POST /pentests/{id}/start`](/api-reference/pentests/start-pentest) | +| `discard_pentest_draft` | Delete a pentest that hasn't started | [`DELETE /pentests/{id}`](/api-reference/pentests/discard-pentest) | + +### PR reviews + +| Name | Description | REST endpoint | +| --------------------------- | -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | +| `get_pr_review_settings` | Read your organization's PR review settings | [`GET /pr-review-settings`](/api-reference/repos/get-pr-review-settings) | +| `update_pr_review_settings` | Update review comments, the check's severity threshold and filters (admins only) | [`PATCH /pr-review-settings`](/api-reference/repos/update-pr-review-settings) | +| `set_pr_reviews` | Turn PR reviews on or off for repositories (admins only) | [`POST /repos/pr-reviews`](/api-reference/repos/set-pr-reviews) | + + + For signed-in testing, your assistant sends login details to Hacktron for + encrypted storage. Anything you type in the conversation also goes to your + assistant's model provider. Use dedicated test credentials. + ## Related @@ -190,9 +227,9 @@ description: "Choose your platform and copy the config. No API key required." icon="key" href="/api-reference/authentication" > - Create and scope the API key used for API-key auth. + Create an API key and choose its access scopes. - The same scans and findings, over the REST API. + Use the same operations from your own scripts and applications. diff --git a/openapi.json b/openapi.json index 9fb975b..5263c37 100644 --- a/openapi.json +++ b/openapi.json @@ -137,6 +137,37 @@ ] } }, + "/scans/{id}/stop": { + "post": { + "operationId": "RestScanController_stop", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RestScanStatusResponseDto" + } + } + } + } + }, + "summary": "Stop a running scan. Members can stop pentests they started and any PR scan; admins can stop any scan.", + "tags": [ + "REST API - Scans" + ] + } + }, "/scans/{id}/status": { "get": { "operationId": "RestScanController_getStatus", @@ -541,6 +572,108 @@ ] } }, + "/findings/counts": { + "get": { + "operationId": "RestFindingController_counts", + "parameters": [ + { + "name": "severity", + "required": false, + "in": "query", + "schema": { + "type": "string", + "enum": [ + "critical", + "high", + "medium", + "low", + "info" + ] + } + }, + { + "name": "state", + "required": false, + "in": "query", + "schema": { + "type": "string", + "enum": [ + "open", + "true_positive", + "false_positive", + "accepted_risk", + "resolved", + "closed" + ] + } + }, + { + "name": "scan_id", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "scan_type", + "required": false, + "in": "query", + "description": "Filter by scan type across all scans associated with the finding.", + "schema": { + "type": "string", + "enum": [ + "pr", + "full" + ] + } + }, + { + "name": "repo_url", + "required": false, + "in": "query", + "description": "Filter by repository URL. Case-insensitive; a trailing slash or .git suffix is ignored.", + "schema": { + "type": "string" + } + }, + { + "name": "found_since", + "required": false, + "in": "query", + "description": "Only include findings discovered on/after this ISO 8601 timestamp.", + "schema": { + "type": "string" + } + }, + { + "name": "found_until", + "required": false, + "in": "query", + "description": "Only include findings discovered before this ISO 8601 timestamp.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RestFindingCountsResponseDto" + } + } + } + } + }, + "summary": "Count findings matching the list filters, by severity, state and repository", + "tags": [ + "REST API - Findings" + ] + } + }, "/findings/export": { "get": { "operationId": "RestFindingController_exportFindings", @@ -1101,799 +1234,2047 @@ "REST API - HFW" ] } - } - }, - "info": { - "title": "Hacktron REST API", - "description": "Public REST API for programmatic access to Hacktron", - "version": "1.0", - "contact": {} - }, - "tags": [], - "servers": [ - { - "url": "https://api.hacktron.ai/v1" - } - ], - "components": { - "securitySchemes": { - "api-key": { - "type": "apiKey", - "in": "header", - "name": "X-Api-Key" - } }, - "schemas": { - "RestRepoDto": { - "type": "object", - "properties": { - "url": { - "type": "string", - "description": "Repository URL" - }, - "branch": { - "type": "string", - "description": "Branch name" - }, - "source": { - "type": "string", - "description": "Repository source", - "enum": [ - "connected", - "public", - "upload" - ], - "default": "connected" + "/pentests": { + "post": { + "operationId": "RestPentestController_create", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RestCreatePentestDto" + } + } } }, - "required": [ - "url", - "branch" - ] - }, - "RestCreateScanDto": { - "type": "object", - "properties": { - "cost_estimation_id": { - "type": "string", - "description": "Completed cost estimation UUID" - }, - "repos": { - "description": "Repositories to scan", - "type": "array", - "items": { - "$ref": "#/components/schemas/RestRepoDto" + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RestPentestResponseDto" + } + } } - }, - "target_urls": { - "description": "Target URLs for pentest", - "type": "array", - "items": { + } + }, + "summary": "Create a draft pentest of repositories or uploaded archives, optionally with live sites", + "tags": [ + "REST API - Pentests" + ] + } + }, + "/pentests/{id}": { + "patch": { + "operationId": "RestPentestController_update", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { "type": "string" } - }, - "auth_instructions": { - "type": "string", - "description": "Target websites and browser login instructions, including URLs and credentials", - "maxLength": 2000 - }, - "custom_context": { - "type": "string", - "description": "Custom context for the pentest", - "maxLength": 2000 - }, - "context_document_ids": { - "description": "Context document UUIDs to include", - "type": "array", - "items": { - "type": "string" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RestUpdatePentestDto" + } } } }, - "required": [ - "cost_estimation_id", - "repos" - ] - }, - "RestCreateScanResponseDto": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "task_id": { + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RestPentestResponseDto" + } + } + } + } + }, + "summary": "Change a pentest that has not started. Changing its repositories clears the estimate.", + "tags": [ + "REST API - Pentests" + ] + }, + "delete": { + "operationId": "RestPentestController_discard", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RestDiscardPentestResponseDto" + } + } + } + } + }, + "summary": "Discard a pentest that has not started", + "tags": [ + "REST API - Pentests" + ] + } + }, + "/pentests/{id}/login-checks": { + "post": { + "operationId": "RestPentestController_checkLogins", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "wait_seconds", + "required": false, + "in": "query", + "description": "Wait up to this many seconds for the work to finish before answering", + "schema": { + "minimum": 0, + "maximum": 50, + "default": 0, + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RestPentestResponseDto" + } + } + } + }, + "400": { + "description": "The pentest has no login targets" + } + }, + "summary": "Check that Hacktron can sign in to each login target, which it must before the pentest starts", + "tags": [ + "REST API - Pentests" + ] + } + }, + "/pentests/{id}/estimate": { + "post": { + "operationId": "RestPentestController_estimate", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RestPentestResponseDto" + } + } + } + } + }, + "summary": "Price a draft pentest; read it at GET /cost-estimations/{cost_estimation_id}", + "tags": [ + "REST API - Pentests" + ] + } + }, + "/pentests/{id}/start": { + "post": { + "operationId": "RestPentestController_start", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RestStartPentestDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RestPentestResponseDto" + } + } + } + }, + "400": { + "description": "A login target has no passing login check from the last hour" + }, + "402": { + "description": "Insufficient credits" + }, + "409": { + "description": "expected_credits no longer matches the total; read the estimate again and confirm the new total" + } + }, + "summary": "Start an estimated pentest, spending the confirmed credits (organization admins only)", + "tags": [ + "REST API - Pentests" + ] + } + }, + "/archives/uploads": { + "post": { + "operationId": "RestArchiveController_requestUpload", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RequestUploadDto" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RequestUploadResponseDto" + } + } + } + } + }, + "summary": "Get a URL to upload a code archive (.zip, .7z or .tar.gz) to, then pentest it as a repo with source upload", + "tags": [ + "REST API - Archives" + ] + } + }, + "/credits": { + "get": { + "operationId": "RestCreditController_balance", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PentestCreditBalanceResponseDto" + } + } + } + } + }, + "summary": "The organization's pentest credit balance", + "tags": [ + "REST API - Credits" + ] + } + }, + "/repos/pr-reviews": { + "post": { + "operationId": "RestRepoController_setPrReviews", + "parameters": [ + { + "name": "X-Organization-Id", + "in": "header", + "description": "Organization ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryIdBatchDto" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositoryBatchResultDto" + } + } + } + } + }, + "summary": "Turn PR/MR reviews on or off for repositories, by their id from the repository list. A repository must be selected for Hacktron access before its reviews can be turned on.", + "tags": [ + "REST API - Repos" + ] + } + }, + "/pr-review-settings": { + "get": { + "operationId": "RestPrReviewSettingsController_get", + "parameters": [ + { + "name": "X-Organization-Id", + "in": "header", + "description": "Organization ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RestPrReviewSettingsDto" + } + } + } + } + }, + "summary": "Get the organization's PR/MR review settings: whether reviews post comments, the severity that fails the PR check, and which PRs are reviewed", + "tags": [ + "REST API - Repos" + ] + }, + "patch": { + "operationId": "RestPrReviewSettingsController_update", + "parameters": [ + { + "name": "X-Organization-Id", + "in": "header", + "description": "Organization ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RestPrReviewSettingsDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RestPrReviewSettingsDto" + } + } + } + } + }, + "summary": "Change the organization's PR/MR review settings. Only the fields sent change; null turns off the fail-on severity or clears a filter. A repository's .hacktron/config.yaml overrides these.", + "tags": [ + "REST API - Repos" + ] + } + } + }, + "info": { + "title": "Hacktron REST API", + "description": "Public REST API for programmatic access to Hacktron", + "version": "1.0", + "contact": {} + }, + "tags": [], + "servers": [ + { + "url": "https://api.hacktron.ai/v1" + } + ], + "components": { + "securitySchemes": { + "api-key": { + "type": "apiKey", + "in": "header", + "name": "X-Api-Key" + } + }, + "schemas": { + "RestRepoDto": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "Repository URL" + }, + "branch": { + "type": "string", + "description": "Branch name" + }, + "source": { + "type": "string", + "description": "Repository source", + "enum": [ + "connected", + "public", + "upload" + ], + "default": "connected" + } + }, + "required": [ + "url", + "branch" + ] + }, + "RestCreateScanDto": { + "type": "object", + "properties": { + "cost_estimation_id": { + "type": "string", + "description": "Completed cost estimation UUID" + }, + "repos": { + "description": "Repositories to scan", + "type": "array", + "items": { + "$ref": "#/components/schemas/RestRepoDto" + } + }, + "target_urls": { + "description": "Target URLs for pentest", + "type": "array", + "items": { + "type": "string" + } + }, + "auth_instructions": { + "type": "string", + "description": "Target websites and browser login instructions, including URLs and credentials", + "maxLength": 2000 + }, + "custom_context": { + "type": "string", + "description": "Custom context for the pentest", + "maxLength": 2000 + }, + "context_document_ids": { + "description": "Context document UUIDs to include", + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "cost_estimation_id", + "repos" + ] + }, + "RestCreateScanResponseDto": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "task_id": { + "type": "string" + }, + "status": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "id", + "task_id", + "status", + "message" + ] + }, + "RestScanStatusResponseDto": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "task_id": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "pending", + "running", + "completed", + "failed", + "stopped", + "cancelled", + "skipped", + "pending_verification", + "pending_triage", + "draft", + "estimating", + "estimated" + ] + }, + "created_at": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + }, + "required": [ + "id", + "task_id", + "status", + "created_at", + "updated_at" + ] + }, + "RestScanFindingsSummaryDto": { + "type": "object", + "properties": { + "critical": { + "type": "number" + }, + "high": { + "type": "number" + }, + "medium": { + "type": "number" + }, + "low": { + "type": "number" + }, + "info": { + "type": "number" + } + }, + "required": [ + "critical", + "high", + "medium", + "low", + "info" + ] + }, + "RestScanResponseDto": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "task_id": { + "type": "string" + }, + "scan_type": { + "type": "string", + "enum": [ + "pr", + "full" + ] + }, + "status": { + "type": "string" + }, + "name": { + "type": "string", + "nullable": true + }, + "repo_url": { + "type": "string", + "nullable": true + }, + "branch": { + "type": "string", + "nullable": true + }, + "pr_number": { + "type": "number", + "nullable": true + }, + "target_urls": { + "nullable": true, + "type": "array", + "items": { + "type": "string" + } + }, + "findings_summary": { + "$ref": "#/components/schemas/RestScanFindingsSummaryDto" + }, + "created_at": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + }, + "required": [ + "id", + "task_id", + "scan_type", + "status", + "name", + "repo_url", + "branch", + "pr_number", + "target_urls", + "findings_summary", + "created_at", + "updated_at" + ] + }, + "RestScanListResponseDto": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RestScanResponseDto" + } + }, + "total": { + "type": "number" + }, + "page": { + "type": "number" + }, + "limit": { + "type": "number" + } + }, + "required": [ + "data", + "total", + "page", + "limit" + ] + }, + "RestFindingResponseDto": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Finding UUID" + }, + "display_id": { + "type": "string", + "description": "Human-readable id (KEY-FD-NUMBER).", + "example": "HAC-FD-1104" + }, + "number": { + "type": "integer", + "description": "Per-organization sequence number behind display_id.", + "example": 1104 + }, + "title": { + "type": "string" + }, + "category": { + "type": "string" + }, + "severity": { + "type": "string", + "enum": [ + "critical", + "high", + "medium", + "low", + "info" + ] + }, + "state": { + "type": "string", + "enum": [ + "open", + "true_positive", + "false_positive", + "accepted_risk", + "resolved", + "closed" + ] + }, + "description": { + "type": "string" + }, + "affected_file": { + "type": "string" + }, + "affected_code": { + "type": "string" + }, + "proof_of_concept": { + "type": "string", + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "scan_id": { + "type": "string", + "nullable": true + }, + "found_at": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "resolved_at": { + "type": "string", + "nullable": true, + "description": "Timestamp of the most recent transition to resolved (ISO 8601). Null if never resolved." + } + }, + "required": [ + "id", + "display_id", + "number", + "title", + "category", + "severity", + "state", + "description", + "affected_file", + "affected_code", + "proof_of_concept", + "tags", + "scan_id", + "found_at", + "updated_at", + "resolved_at" + ] + }, + "RestFindingListResponseDto": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RestFindingResponseDto" + } + }, + "total": { + "type": "number" + }, + "page": { + "type": "number" + }, + "limit": { + "type": "number" + } + }, + "required": [ + "data", + "total", + "page", + "limit" + ] + }, + "RestSeverityCountsDto": { + "type": "object", + "properties": { + "critical": { + "type": "number" + }, + "high": { + "type": "number" + }, + "medium": { + "type": "number" + }, + "low": { + "type": "number" + }, + "info": { + "type": "number" + } + }, + "required": [ + "critical", + "high", + "medium", + "low", + "info" + ] + }, + "RestStateCountsDto": { + "type": "object", + "properties": { + "open": { + "type": "number" + }, + "true_positive": { + "type": "number" + }, + "false_positive": { + "type": "number" + }, + "accepted_risk": { + "type": "number" + }, + "resolved": { + "type": "number" + }, + "closed": { + "type": "number" + } + }, + "required": [ + "open", + "true_positive", + "false_positive", + "accepted_risk", + "resolved", + "closed" + ] + }, + "RestRepositoryFindingCountsDto": { + "type": "object", + "properties": { + "repository": { + "type": "string", + "nullable": true, + "description": "Repository URL, or null for findings with no repository" + }, + "total": { + "type": "number" + }, + "by_severity": { + "$ref": "#/components/schemas/RestSeverityCountsDto" + } + }, + "required": [ + "repository", + "total", + "by_severity" + ] + }, + "RestFindingCountsResponseDto": { + "type": "object", + "properties": { + "total": { + "type": "number" + }, + "by_severity": { + "$ref": "#/components/schemas/RestSeverityCountsDto" + }, + "by_state": { + "$ref": "#/components/schemas/RestStateCountsDto" + }, + "by_repository": { + "description": "Most findings first", + "type": "array", + "items": { + "$ref": "#/components/schemas/RestRepositoryFindingCountsDto" + } + } + }, + "required": [ + "total", + "by_severity", + "by_state", + "by_repository" + ] + }, + "RestUpdateFindingDto": { + "type": "object", + "properties": { + "state": { + "type": "string", + "enum": [ + "open", + "true_positive", + "false_positive", + "accepted_risk", + "resolved", + "closed" + ], + "description": "New finding state" + }, + "severity": { + "type": "string", + "enum": [ + "critical", + "high", + "medium", + "low", + "info" + ], + "description": "New severity level" + }, + "state_reason": { + "type": "string", + "description": "Reason for the state change (defaults to reason if omitted)" + }, + "severity_reason": { + "type": "string", + "description": "Reason for the severity change (defaults to reason if omitted)" + }, + "reason": { + "type": "string", + "description": "Shorthand reason applied to both state and severity changes when their specific reason is not provided" + }, + "duplicate_of": { + "type": "string", + "nullable": true, + "description": "UUID of the canonical finding to mark this finding as a duplicate of, or null to unmark. The target must be in the same repository." + } + } + }, + "RestUpdateFindingResponseDto": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "state": { + "type": "string", + "enum": [ + "open", + "true_positive", + "false_positive", + "accepted_risk", + "resolved", + "closed" + ] + }, + "severity": { + "type": "string", + "enum": [ + "critical", + "high", + "medium", + "low", + "info" + ] + }, + "duplicate_of": { + "type": "string", + "nullable": true, + "description": "UUID of the canonical finding this duplicates, if any." + } + }, + "required": [ + "id", + "state", + "severity", + "duplicate_of" + ] + }, + "RestAddCommentDto": { + "type": "object", + "properties": { + "comment": { + "type": "string", + "description": "Comment text", + "maxLength": 4000 + } + }, + "required": [ + "comment" + ] + }, + "RestAddCommentResponseDto": { + "type": "object", + "properties": { + "comment_id": { + "type": "string" + } + }, + "required": [ + "comment_id" + ] + }, + "RestTriageThreadEntryDto": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "reaction": { + "type": "string", + "nullable": true + }, + "comment": { + "type": "string", + "nullable": true + }, + "user_id": { + "type": "string" + }, + "username": { + "type": "string" + }, + "source": { + "type": "string" + }, + "timestamp": { + "type": "string" + } + }, + "required": [ + "id", + "reaction", + "comment", + "user_id", + "source", + "timestamp" + ] + }, + "TaintStepDto": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "source", + "propagation", + "sink" + ] + }, + "file": { "type": "string" }, - "status": { + "line": { + "type": "number" + }, + "end_line": { + "type": "number" + }, + "symbol": { + "type": "string" + }, + "description": { + "type": "string" + }, + "detail": { + "type": "string", + "description": "Raw symbol docstring; supplemental context for the stepped taint flow." + }, + "taint_sources": { + "type": "array", + "items": { + "type": "string" + } + }, + "code": { + "type": "string", + "description": "Source code lines for this step's symbol, pre-sliced by the worker." + }, + "code_start_line": { + "type": "number", + "description": "1-based start line number of the `code` slice in the source file. Used by the frontend as the anchor for chevron-expand requests to GET /findings/:uuid/source." + }, + "code_end_line": { + "type": "number", + "description": "1-based end line number of the `code` slice in the source file. Used by the frontend as the anchor for chevron-expand requests to GET /findings/:uuid/source." + } + }, + "required": [ + "type", + "file", + "line", + "end_line", + "symbol", + "description", + "taint_sources" + ] + }, + "RestFindingDetailResponseDto": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Finding UUID" + }, + "display_id": { + "type": "string", + "description": "Human-readable id (KEY-FD-NUMBER).", + "example": "HAC-FD-1104" + }, + "number": { + "type": "integer", + "description": "Per-organization sequence number behind display_id.", + "example": 1104 + }, + "title": { + "type": "string" + }, + "category": { + "type": "string" + }, + "severity": { + "type": "string", + "enum": [ + "critical", + "high", + "medium", + "low", + "info" + ] + }, + "state": { + "type": "string", + "enum": [ + "open", + "true_positive", + "false_positive", + "accepted_risk", + "resolved", + "closed" + ] + }, + "description": { + "type": "string" + }, + "affected_file": { + "type": "string" + }, + "affected_code": { + "type": "string" + }, + "proof_of_concept": { + "type": "string", + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "scan_id": { + "type": "string", + "nullable": true + }, + "found_at": { "type": "string" }, - "message": { - "type": "string" + "updated_at": { + "type": "string" + }, + "resolved_at": { + "type": "string", + "nullable": true, + "description": "Timestamp of the most recent transition to resolved (ISO 8601). Null if never resolved." + }, + "triage_thread": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RestTriageThreadEntryDto" + } + }, + "taint_path": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaintStepDto" + } + }, + "occurrence_count": { + "type": "number" + }, + "repo_url": { + "type": "string", + "nullable": true + }, + "scan_type": { + "type": "string", + "nullable": true + } + }, + "required": [ + "id", + "display_id", + "number", + "title", + "category", + "severity", + "state", + "description", + "affected_file", + "affected_code", + "proof_of_concept", + "tags", + "scan_id", + "found_at", + "updated_at", + "resolved_at", + "triage_thread", + "taint_path", + "occurrence_count", + "repo_url", + "scan_type" + ] + }, + "ConnectedCostEstimationRepoDto": { + "type": "object", + "properties": { + "source": { + "type": "string", + "description": "Source type", + "enum": [ + "connected" + ], + "default": "connected" + }, + "repo_url": { + "type": "string", + "description": "Repository URL" + }, + "branch": { + "type": "string", + "description": "Branch name" + }, + "github_installation_id": { + "type": "number", + "description": "GitHub App installation ID. Omitted for PAT-based connections (e.g. GitHub Enterprise), where the installation is resolved from repo_url and the organization instead." + } + }, + "required": [ + "source", + "repo_url", + "branch" + ] + }, + "PublicCostEstimationRepoDto": { + "type": "object", + "properties": { + "source": { + "type": "string", + "description": "Source type", + "enum": [ + "public" + ] + }, + "repo_url": { + "type": "string", + "description": "Repository URL" + }, + "branch": { + "type": "string", + "description": "Branch name" + } + }, + "required": [ + "source", + "repo_url", + "branch" + ] + }, + "UploadCostEstimationRepoDto": { + "type": "object", + "properties": { + "source": { + "type": "string", + "description": "Source type", + "enum": [ + "upload" + ] + }, + "archive_id": { + "type": "string", + "description": "Scan archive ID" } }, "required": [ - "id", - "task_id", - "status", - "message" + "source", + "archive_id" ] }, - "RestScanFindingsSummaryDto": { + "CreateCostEstimationDto": { "type": "object", "properties": { - "critical": { - "type": "number" - }, - "high": { - "type": "number" - }, - "medium": { - "type": "number" - }, - "low": { - "type": "number" + "name": { + "type": "string", + "description": "Friendly name for this estimation" }, - "info": { - "type": "number" + "repos": { + "type": "array", + "description": "Repositories to estimate", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/ConnectedCostEstimationRepoDto" + }, + { + "$ref": "#/components/schemas/PublicCostEstimationRepoDto" + }, + { + "$ref": "#/components/schemas/UploadCostEstimationRepoDto" + } + ], + "discriminator": { + "propertyName": "source", + "mapping": { + "connected": "#/components/schemas/ConnectedCostEstimationRepoDto", + "public": "#/components/schemas/PublicCostEstimationRepoDto", + "upload": "#/components/schemas/UploadCostEstimationRepoDto" + } + } + } } }, "required": [ - "critical", - "high", - "medium", - "low", - "info" + "repos" ] }, - "RestScanResponseDto": { + "RepoInputResponseDto": { "type": "object", "properties": { - "id": { - "type": "string" - }, - "task_id": { - "type": "string" - }, - "scan_type": { + "source": { "type": "string", + "description": "Source type", "enum": [ - "pr", - "full" + "connected", + "public", + "upload" ] }, - "status": { - "type": "string" - }, - "name": { - "type": "string", - "nullable": true - }, "repo_url": { "type": "string", - "nullable": true + "description": "Repository URL", + "example": "https://github.com/owner/repo" }, "branch": { "type": "string", - "nullable": true - }, - "pr_number": { - "type": "number", - "nullable": true - }, - "target_urls": { - "nullable": true, - "type": "array", - "items": { - "type": "string" - } - }, - "findings_summary": { - "$ref": "#/components/schemas/RestScanFindingsSummaryDto" + "description": "Branch name", + "example": "main" }, - "created_at": { - "type": "string" + "commit_sha": { + "type": "string", + "description": "Resolved commit SHA", + "example": "abc123def456" }, - "updated_at": { - "type": "string" + "archive_id": { + "type": "string", + "description": "Archive ID (for upload sources)" } - }, - "required": [ - "id", - "task_id", - "scan_type", - "status", - "name", - "repo_url", - "branch", - "pr_number", - "target_urls", - "findings_summary", - "created_at", - "updated_at" - ] + } }, - "RestScanListResponseDto": { + "RepoResultApplicationDto": { "type": "object", "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/RestScanResponseDto" - } - }, - "total": { - "type": "number" + "app_name": { + "type": "string", + "description": "Application name", + "example": "backend-api" }, - "page": { - "type": "number" + "app_root_path": { + "type": "string", + "description": "Application root path", + "example": "apps/api" }, - "limit": { - "type": "number" + "architecture": { + "type": "string", + "description": "Architecture description", + "example": "NestJS REST API with PostgreSQL" } }, "required": [ - "data", - "total", - "page", - "limit" + "app_name", + "app_root_path" ] }, - "RestScanStatusResponseDto": { + "RepoResultResponseDto": { "type": "object", "properties": { - "id": { - "type": "string" + "repo_url": { + "type": "string", + "description": "Repository URL", + "example": "https://github.com/owner/repo" }, - "task_id": { - "type": "string" + "branch": { + "type": "string", + "description": "Branch name", + "example": "main" + }, + "commit_sha": { + "type": "string", + "description": "Commit SHA", + "example": "abc123def456" }, "status": { "type": "string", + "description": "Result status", "enum": [ - "pending", - "running", "completed", - "failed", - "stopped", - "cancelled", - "skipped", - "pending_verification", - "pending_triage", - "draft", - "estimating", - "estimated" + "failed" + ], + "example": "completed" + }, + "from_cache": { + "type": "boolean", + "description": "Whether result was served from cache", + "example": false + }, + "applications": { + "description": "Detected applications", + "type": "array", + "items": { + "$ref": "#/components/schemas/RepoResultApplicationDto" + } + }, + "credits": { + "type": "number", + "description": "Estimated credits for this repo", + "example": 4000 + }, + "error": { + "type": "string", + "description": "Error message (when status is failed)" + }, + "failure_reason": { + "type": "string", + "description": "Structured failure reason", + "enum": [ + "unsupported_language", + "scope_no_match" ] }, - "created_at": { - "type": "string" + "unsupported_languages": { + "description": "Detected unsupported languages among the top languages", + "type": "array", + "items": { + "type": "string" + } }, - "updated_at": { - "type": "string" + "scoped_file_count": { + "type": "number", + "description": "Files that matched the repository's whitebox scope rules, counted on disk before analysis. Absent for unscoped repositories.", + "example": 42 } }, "required": [ - "id", - "task_id", + "repo_url", "status", - "created_at", - "updated_at" + "from_cache" ] }, - "RestFindingResponseDto": { + "CostEstimationResponseDto": { "type": "object", "properties": { "id": { "type": "string", - "description": "Finding UUID" - }, - "display_id": { - "type": "string", - "description": "Human-readable id (KEY-FD-NUMBER).", - "example": "HAC-FD-1104" + "description": "Estimation UUID", + "example": "123e4567-e89b-12d3-a456-426614174000" }, - "number": { - "type": "integer", - "description": "Per-organization sequence number behind display_id.", - "example": 1104 + "organization_id": { + "type": "string", + "description": "Organization UUID", + "example": "f336d0bc-b841-465b-8045-024475c079dd" }, - "title": { - "type": "string" + "user_id": { + "type": "string", + "description": "User UUID", + "example": "f336d0bc-b841-465b-8045-024475c079dd" }, - "category": { - "type": "string" + "name": { + "type": "string", + "description": "Friendly name for this estimation" }, - "severity": { + "task_id": { "type": "string", - "enum": [ - "critical", - "high", - "medium", - "low", - "info" - ] + "description": "Task identifier", + "example": "cost_est_owner-repo_12345" }, - "state": { + "status": { "type": "string", + "description": "Estimation status", "enum": [ - "open", - "true_positive", - "false_positive", - "accepted_risk", - "resolved", - "closed" - ] - }, - "description": { - "type": "string" - }, - "affected_file": { - "type": "string" + "pending", + "running", + "completed", + "partial", + "failed" + ], + "example": "completed" }, - "affected_code": { - "type": "string" + "repos": { + "description": "Input repositories", + "type": "array", + "items": { + "$ref": "#/components/schemas/RepoInputResponseDto" + } }, - "proof_of_concept": { - "type": "string", - "nullable": true + "total_credits": { + "type": "number", + "description": "Total estimated credits across all repos", + "example": 8000 }, - "tags": { + "repo_results": { + "description": "Per-repo estimation results", "type": "array", "items": { - "type": "string" + "$ref": "#/components/schemas/RepoResultResponseDto" } }, - "scan_id": { + "error": { "type": "string", - "nullable": true + "description": "Error message" }, - "found_at": { - "type": "string" + "triage_required": { + "type": "boolean", + "description": "Whether human triage is mandatory for a scan of this estimation (large app, above the predicted-cost threshold). The customer cannot opt out when true.", + "example": false }, - "updated_at": { - "type": "string" + "triage_fee_credits": { + "type": "number", + "description": "Triage fee in credits (a percentage of the scan base credits) added when triage is enabled", + "example": 1600 }, - "resolved_at": { + "created_at": { + "format": "date-time", "type": "string", - "nullable": true, - "description": "Timestamp of the most recent transition to resolved (ISO 8601). Null if never resolved." + "description": "Timestamp when estimation was created", + "example": "2026-03-10T12:00:00.000Z" + }, + "updated_at": { + "format": "date-time", + "type": "string", + "description": "Timestamp when estimation was last updated", + "example": "2026-03-10T12:05:00.000Z" } }, "required": [ "id", - "display_id", - "number", - "title", - "category", - "severity", - "state", - "description", - "affected_file", - "affected_code", - "proof_of_concept", - "tags", - "scan_id", - "found_at", - "updated_at", - "resolved_at" + "organization_id", + "user_id", + "task_id", + "status", + "repos", + "triage_required", + "triage_fee_credits", + "created_at", + "updated_at" ] }, - "RestFindingListResponseDto": { + "RestCostEstimationListResponseDto": { "type": "object", "properties": { "data": { "type": "array", "items": { - "$ref": "#/components/schemas/RestFindingResponseDto" + "$ref": "#/components/schemas/CostEstimationResponseDto" } }, "total": { "type": "number" - }, - "page": { - "type": "number" - }, - "limit": { - "type": "number" } }, "required": [ "data", - "total", - "page", - "limit" + "total" ] }, - "RestUpdateFindingDto": { + "UnifiedRepoDto": { "type": "object", "properties": { - "state": { + "id": { "type": "string", - "enum": [ - "open", - "true_positive", - "false_positive", - "accepted_risk", - "resolved", - "closed" - ], - "description": "New finding state" + "description": "Internal UUID", + "example": "123e4567-e89b-12d3-a456-426614174000" }, - "severity": { + "provider": { "type": "string", + "description": "Source provider", "enum": [ - "critical", - "high", - "medium", - "low", - "info" + "github", + "gitlab", + "bitbucket", + "azure" ], - "description": "New severity level" + "example": "github" }, - "state_reason": { + "name": { "type": "string", - "description": "Reason for the state change (defaults to reason if omitted)" + "description": "Display name (owner/repo for GitHub, namespace/project for GitLab, workspace/slug for Bitbucket, project/repository for Azure DevOps)", + "example": "octocat/Hello-World" }, - "severity_reason": { + "providerKey": { "type": "string", - "description": "Reason for the severity change (defaults to reason if omitted)" + "description": "Provider-native identifier for mutation endpoints. GitHub: owner/repo, GitLab: numeric projectId (as string), Bitbucket: repository UUID, Azure DevOps: repository UUID.", + "example": "octocat/Hello-World" }, - "reason": { - "type": "string", - "description": "Shorthand reason applied to both state and severity changes when their specific reason is not provided" + "enabled": { + "type": "boolean", + "description": "Whether the repository is enabled for scanning", + "example": true }, - "duplicate_of": { + "threatModelUpdatedAt": { "type": "string", "nullable": true, - "description": "UUID of the canonical finding to mark this finding as a duplicate of, or null to unmark. The target must be in the same repository." - } - } - }, - "RestUpdateFindingResponseDto": { - "type": "object", - "properties": { - "id": { - "type": "string" + "description": "ISO timestamp of the repo's most recent threat_model.md update, or null if it has none. Repos are returned ordered by this (most recent first; repos without a threat model last).", + "example": "2026-06-22T12:00:00.000Z" }, - "state": { + "openFindingsCount": { + "type": "number", + "description": "Number of approved, visible findings that remain open for this repository", + "example": 3 + }, + "lastScanAt": { "type": "string", - "enum": [ - "open", - "true_positive", - "false_positive", - "accepted_risk", - "resolved", - "closed" - ] + "nullable": true, + "description": "ISO timestamp of the most recent completed scan containing this repository, or null if it has not been scanned", + "example": "2026-09-15T12:00:00.000Z" }, - "severity": { + "ossApproved": { + "type": "boolean", + "description": "Whether this github.com repository is approved for free OSS PR reviews. Independent of whether PR scans are enabled.", + "example": true + }, + "prScansEnabled": { + "type": "boolean", + "description": "Whether PR/MR scans are enabled (GitHub and GitLab)", + "example": true + }, + "prScanBranches": { + "description": "Target branch patterns for PR/MR scans (GitHub and GitLab)", + "example": [ + "main", + "**/release/**" + ], + "type": "array", + "items": { + "type": "string" + } + }, + "installationId": { + "type": "number", + "description": "GitHub App installation ID (GitHub only)", + "example": 12345678 + }, + "baseUrl": { "type": "string", - "enum": [ - "critical", - "high", - "medium", - "low", - "info" - ] + "nullable": true, + "description": "GitHub Enterprise base URL for self-hosted (PAT) installations (null/absent for github.com), the GitLab installation URL, or the Azure DevOps organization URL. Clients should build clone/display URLs from this host instead of assuming the public provider host.", + "example": "https://ghe.example.com" }, - "duplicate_of": { + "defaultBranch": { "type": "string", - "nullable": true, - "description": "UUID of the canonical finding this duplicates, if any." - } - }, - "required": [ - "id", - "state", - "severity", - "duplicate_of" - ] - }, - "RestAddCommentDto": { - "type": "object", - "properties": { - "comment": { + "description": "Default branch (GitLab only)", + "example": "main" + }, + "visibility": { "type": "string", - "description": "Comment text", - "maxLength": 4000 + "description": "Project visibility (GitLab only)", + "example": "private" + }, + "webhookRegistered": { + "type": "boolean", + "description": "Whether a webhook is registered for this project (GitLab only)", + "example": true + }, + "isPrivate": { + "type": "boolean", + "description": "Whether the repository is private (Bitbucket only)", + "example": true } }, "required": [ - "comment" + "id", + "provider", + "name", + "providerKey", + "enabled", + "threatModelUpdatedAt", + "openFindingsCount", + "lastScanAt" ] }, - "RestAddCommentResponseDto": { + "MergedReposResponseDto": { "type": "object", "properties": { - "comment_id": { - "type": "string" + "repos": { + "description": "Unified list of repositories from all providers", + "type": "array", + "items": { + "$ref": "#/components/schemas/UnifiedRepoDto" + } + }, + "errors": { + "description": "Providers that failed to respond", + "example": [], + "type": "array", + "items": { + "type": "string" + } } }, "required": [ - "comment_id" + "repos", + "errors" ] }, - "RestTriageThreadEntryDto": { + "RestHfwBatchPackageDto": { "type": "object", "properties": { - "id": { - "type": "string" - }, - "reaction": { - "type": "string", - "nullable": true - }, - "comment": { + "ecosystem": { "type": "string", - "nullable": true - }, - "user_id": { - "type": "string" - }, - "username": { - "type": "string" + "enum": [ + "npm", + "pypi" + ] }, - "source": { + "package": { "type": "string" }, - "timestamp": { + "version": { "type": "string" } }, "required": [ - "id", - "reaction", - "comment", - "user_id", - "source", - "timestamp" + "ecosystem", + "package", + "version" ] }, - "TaintStepDto": { + "RestHfwBatchRequestDto": { "type": "object", "properties": { - "type": { - "type": "string", - "enum": [ - "source", - "propagation", - "sink" - ] - }, - "file": { - "type": "string" - }, - "line": { - "type": "number" - }, - "end_line": { - "type": "number" - }, - "symbol": { - "type": "string" - }, - "description": { - "type": "string" - }, - "detail": { - "type": "string", - "description": "Raw symbol docstring; supplemental context for the stepped taint flow." - }, - "taint_sources": { + "packages": { "type": "array", "items": { - "type": "string" + "$ref": "#/components/schemas/RestHfwBatchPackageDto" } - }, - "code": { - "type": "string", - "description": "Source code lines for this step's symbol, pre-sliced by the worker." - }, - "code_start_line": { - "type": "number", - "description": "1-based start line number of the `code` slice in the source file. Used by the frontend as the anchor for chevron-expand requests to GET /findings/:uuid/source." - }, - "code_end_line": { - "type": "number", - "description": "1-based end line number of the `code` slice in the source file. Used by the frontend as the anchor for chevron-expand requests to GET /findings/:uuid/source." } }, "required": [ - "type", - "file", - "line", - "end_line", - "symbol", - "description", - "taint_sources" + "packages" ] }, - "RestFindingDetailResponseDto": { + "RestHfwVerdictResponseDto": { "type": "object", "properties": { - "id": { + "status": { "type": "string", - "description": "Finding UUID" + "enum": [ + "ready", + "pending", + "scanning" + ] }, - "display_id": { + "ecosystem": { "type": "string", - "description": "Human-readable id (KEY-FD-NUMBER).", - "example": "HAC-FD-1104" - }, - "number": { - "type": "integer", - "description": "Per-organization sequence number behind display_id.", - "example": 1104 + "enum": [ + "npm", + "pypi" + ] }, - "title": { + "package": { "type": "string" }, - "category": { + "version": { "type": "string" }, - "severity": { - "type": "string", - "enum": [ - "critical", - "high", - "medium", - "low", - "info" - ] - }, - "state": { + "level": { "type": "string", "enum": [ - "open", - "true_positive", - "false_positive", - "accepted_risk", - "resolved", - "closed" + "clean", + "suspicious", + "malicious", + "unknown" ] }, - "description": { + "confidence": { "type": "string" }, - "affected_file": { + "summary": { "type": "string" }, - "affected_code": { + "findings": { + "type": "array", + "items": { + "type": "object" + } + }, + "source": { "type": "string" }, - "proof_of_concept": { - "type": "string", - "nullable": true + "osv_id": { + "type": "string" + } + }, + "required": [ + "status", + "ecosystem", + "package", + "version", + "level", + "confidence", + "summary", + "findings" + ] + }, + "RestHfwBatchResponseDto": { + "type": "object", + "properties": { + "status": { + "type": "string" }, - "tags": { + "verdicts": { "type": "array", "items": { - "type": "string" + "$ref": "#/components/schemas/RestHfwVerdictResponseDto" } }, - "scan_id": { - "type": "string", - "nullable": true - }, - "found_at": { + "errors": { + "type": "array", + "items": { + "type": "object" + } + } + }, + "required": [ + "status", + "verdicts", + "errors" + ] + }, + "RestHfwOsvFeedResultDto": { + "type": "object", + "properties": { + "ecosystem": { "type": "string" }, - "updated_at": { + "package": { "type": "string" }, - "resolved_at": { - "type": "string", - "nullable": true, - "description": "Timestamp of the most recent transition to resolved (ISO 8601). Null if never resolved." - }, - "triage_thread": { + "versions": { "type": "array", "items": { - "$ref": "#/components/schemas/RestTriageThreadEntryDto" + "type": "string" } + } + }, + "required": [ + "ecosystem", + "package", + "versions" + ] + }, + "RestHfwOsvFeedResponseDto": { + "type": "object", + "properties": { + "total": { + "type": "number" }, - "taint_path": { + "results": { "type": "array", "items": { - "$ref": "#/components/schemas/TaintStepDto" + "$ref": "#/components/schemas/RestHfwOsvFeedResultDto" } - }, - "occurrence_count": { - "type": "number" - }, - "repo_url": { - "type": "string", - "nullable": true - }, - "scan_type": { - "type": "string", - "nullable": true } }, "required": [ - "id", - "display_id", - "number", - "title", - "category", - "severity", - "state", - "description", - "affected_file", - "affected_code", - "proof_of_concept", - "tags", - "scan_id", - "found_at", - "updated_at", - "resolved_at", - "triage_thread", - "taint_path", - "occurrence_count", - "repo_url", - "scan_type" + "results" ] }, - "ConnectedCostEstimationRepoDto": { + "ConnectedRepoInputDto": { "type": "object", "properties": { + "include_paths": { + "description": "Whitebox scope: gitignore-style globs (relative to the repo root) that narrow the scan to matching paths. Empty means the whole repository.", + "example": [ + "services/gateway/**" + ], + "type": "array", + "items": { + "type": "string" + } + }, + "exclude_paths": { + "description": "Whitebox scope: gitignore-style globs removed from the scan after include_paths is applied; use `*.md` to drop a file type.", + "example": [ + "**/tests/**", + "vendor/", + "*.md" + ], + "type": "array", + "items": { + "type": "string" + } + }, "source": { "type": "string", - "description": "Source type", "enum": [ "connected" - ], - "default": "connected" + ] }, - "repo_url": { + "url": { "type": "string", - "description": "Repository URL" + "description": "Repository URL", + "example": "https://github.com/owner/repo" }, "branch": { "type": "string", - "description": "Branch name" + "description": "Branch name, tag name, or commit SHA" + }, + "ref_type": { + "type": "string", + "enum": [ + "branch", + "tag", + "sha" + ], + "description": "How `branch` was selected (UI hint for draft re-use)" }, "github_installation_id": { "type": "number", - "description": "GitHub App installation ID. Omitted for PAT-based connections (e.g. GitHub Enterprise), where the installation is resolved from repo_url and the organization instead." + "description": "GitHub App installation id. Omitted for PAT-based connections (e.g. GitHub Enterprise), where the installation is resolved from the repository URL and the organization instead." } }, "required": [ "source", - "repo_url", + "url", "branch" ] }, - "PublicCostEstimationRepoDto": { + "PublicRepoInputDto": { "type": "object", "properties": { + "include_paths": { + "description": "Whitebox scope: gitignore-style globs (relative to the repo root) that narrow the scan to matching paths. Empty means the whole repository.", + "example": [ + "services/gateway/**" + ], + "type": "array", + "items": { + "type": "string" + } + }, + "exclude_paths": { + "description": "Whitebox scope: gitignore-style globs removed from the scan after include_paths is applied; use `*.md` to drop a file type.", + "example": [ + "**/tests/**", + "vendor/", + "*.md" + ], + "type": "array", + "items": { + "type": "string" + } + }, "source": { "type": "string", - "description": "Source type", "enum": [ "public" ] }, - "repo_url": { + "url": { "type": "string", - "description": "Repository URL" + "description": "Repository URL", + "example": "https://github.com/owner/repo" }, "branch": { "type": "string", - "description": "Branch name" + "description": "Branch name, tag name, or commit SHA" + }, + "ref_type": { + "type": "string", + "enum": [ + "branch", + "tag", + "sha" + ], + "description": "How `branch` was selected (UI hint for draft re-use)" } }, "required": [ "source", - "repo_url", + "url", "branch" ] }, - "UploadCostEstimationRepoDto": { + "UploadRepoInputDto": { "type": "object", "properties": { + "include_paths": { + "description": "Whitebox scope: gitignore-style globs (relative to the repo root) that narrow the scan to matching paths. Empty means the whole repository.", + "example": [ + "services/gateway/**" + ], + "type": "array", + "items": { + "type": "string" + } + }, + "exclude_paths": { + "description": "Whitebox scope: gitignore-style globs removed from the scan after include_paths is applied; use `*.md` to drop a file type.", + "example": [ + "**/tests/**", + "vendor/", + "*.md" + ], + "type": "array", + "items": { + "type": "string" + } + }, "source": { "type": "string", - "description": "Source type", "enum": [ "upload" ] @@ -1908,418 +3289,469 @@ "archive_id" ] }, - "CreateCostEstimationDto": { + "RestLoginTargetDto": { "type": "object", "properties": { - "name": { + "site": { "type": "string", - "description": "Friendly name for this estimation" + "description": "A deployed copy of the app to test signed in, e.g. https://staging.acme.dev", + "maxLength": 2048 + }, + "login_instructions": { + "type": "string", + "description": "How to sign in: the login page, username, password and any steps. Stored encrypted and never returned.", + "maxLength": 2000 + } + }, + "required": [ + "site", + "login_instructions" + ] + }, + "RestCreatePentestDto": { + "type": "object", + "properties": { + "target_urls": { + "description": "Target URLs (pentest only)", + "type": "array", + "items": { + "type": "string" + } + }, + "custom_context": { + "type": "string", + "description": "Custom context (pentest only)" + }, + "application_id": { + "type": "string", + "description": "iva application this scan targets (group of repos)" + }, + "login_targets": { + "description": "Deployed sites to test signed in, each with how to sign in. On update the list replaces the saved one, and [] removes them. A pentest tests live sites either all signed in or all without a login (target_urls). Check each login with POST /pentests/{id}/login-checks before starting.", + "maxItems": 10, + "type": "array", + "items": { + "$ref": "#/components/schemas/RestLoginTargetDto" + } }, "repos": { "type": "array", - "description": "Repositories to estimate", + "description": "Repositories or uploaded archives to test", "items": { "oneOf": [ { - "$ref": "#/components/schemas/ConnectedCostEstimationRepoDto" + "$ref": "#/components/schemas/ConnectedRepoInputDto" }, { - "$ref": "#/components/schemas/PublicCostEstimationRepoDto" + "$ref": "#/components/schemas/PublicRepoInputDto" }, { - "$ref": "#/components/schemas/UploadCostEstimationRepoDto" + "$ref": "#/components/schemas/UploadRepoInputDto" } ], "discriminator": { "propertyName": "source", "mapping": { - "connected": "#/components/schemas/ConnectedCostEstimationRepoDto", - "public": "#/components/schemas/PublicCostEstimationRepoDto", - "upload": "#/components/schemas/UploadCostEstimationRepoDto" + "connected": "#/components/schemas/ConnectedRepoInputDto", + "public": "#/components/schemas/PublicRepoInputDto", + "upload": "#/components/schemas/UploadRepoInputDto" } } - } + }, + "minItems": 1 } }, "required": [ "repos" ] }, - "RepoInputResponseDto": { + "RepoDataDto": { "type": "object", "properties": { + "include_paths": { + "description": "Whitebox scope: gitignore-style globs (relative to the repo root) that narrow the scan to matching paths. Empty means the whole repository.", + "example": [ + "services/gateway/**" + ], + "type": "array", + "items": { + "type": "string" + } + }, + "exclude_paths": { + "description": "Whitebox scope: gitignore-style globs removed from the scan after include_paths is applied; use `*.md` to drop a file type.", + "example": [ + "**/tests/**", + "vendor/", + "*.md" + ], + "type": "array", + "items": { + "type": "string" + } + }, "source": { "type": "string", - "description": "Source type", "enum": [ "connected", "public", "upload" ] }, - "repo_url": { - "type": "string", - "description": "Repository URL", - "example": "https://github.com/owner/repo" - }, - "branch": { - "type": "string", - "description": "Branch name", - "example": "main" - }, - "commit_sha": { - "type": "string", - "description": "Resolved commit SHA", - "example": "abc123def456" - }, - "archive_id": { - "type": "string", - "description": "Archive ID (for upload sources)" - } - } - }, - "RepoResultApplicationDto": { - "type": "object", - "properties": { - "app_name": { - "type": "string", - "description": "Application name", - "example": "backend-api" - }, - "app_root_path": { + "provider": { "type": "string", - "description": "Application root path", - "example": "apps/api" + "enum": [ + "github", + "gitlab", + "bitbucket", + "azure" + ], + "readOnly": true, + "description": "Resolved by the server at scan start; ignored on input" }, - "architecture": { - "type": "string", - "description": "Architecture description", - "example": "NestJS REST API with PostgreSQL" - } - }, - "required": [ - "app_name", - "app_root_path" - ] - }, - "RepoResultResponseDto": { - "type": "object", - "properties": { - "repo_url": { + "url": { "type": "string", "description": "Repository URL", "example": "https://github.com/owner/repo" }, - "branch": { - "type": "string", - "description": "Branch name", - "example": "main" - }, - "commit_sha": { + "branch": { "type": "string", - "description": "Commit SHA", - "example": "abc123def456" + "description": "Branch name, tag name, or commit SHA" }, - "status": { + "ref_type": { "type": "string", - "description": "Result status", "enum": [ - "completed", - "failed" + "branch", + "tag", + "sha" ], - "example": "completed" - }, - "from_cache": { - "type": "boolean", - "description": "Whether result was served from cache", - "example": false + "description": "How `branch` was selected (UI hint for draft re-use)" }, - "applications": { - "description": "Detected applications", - "type": "array", - "items": { - "$ref": "#/components/schemas/RepoResultApplicationDto" - } + "archive_id": { + "type": "string", + "description": "Scan archive ID" }, - "credits": { + "github_installation_id": { "type": "number", - "description": "Estimated credits for this repo", - "example": 4000 + "description": "GitHub App installation ID (connected repos only)" + } + } + }, + "RestPentestLoginTargetDto": { + "type": "object", + "properties": { + "site": { + "type": "string" }, - "error": { + "login_check": { "type": "string", - "description": "Error message (when status is failed)" + "enum": [ + "unverified", + "pending", + "running", + "succeeded", + "failed", + "cancelled", + "expired" + ], + "description": "Whether Hacktron could sign in with the login the site has now. A pass lasts an hour, and changing the site or login makes it unverified." }, "failure_reason": { "type": "string", - "description": "Structured failure reason", - "enum": [ - "unsupported_language", - "scope_no_match" - ] - }, - "unsupported_languages": { - "description": "Detected unsupported languages among the top languages", - "type": "array", - "items": { - "type": "string" - } - }, - "scoped_file_count": { - "type": "number", - "description": "Files that matched the repository's whitebox scope rules, counted on disk before analysis. Absent for unscoped repositories.", - "example": 42 + "nullable": true, + "description": "Why the check failed, without credentials" } }, "required": [ - "repo_url", - "status", - "from_cache" + "site", + "login_check", + "failure_reason" ] }, - "CostEstimationResponseDto": { + "RestPentestResponseDto": { "type": "object", "properties": { - "id": { - "type": "string", - "description": "Estimation UUID", - "example": "123e4567-e89b-12d3-a456-426614174000" - }, - "organization_id": { - "type": "string", - "description": "Organization UUID", - "example": "f336d0bc-b841-465b-8045-024475c079dd" - }, - "user_id": { - "type": "string", - "description": "User UUID", - "example": "f336d0bc-b841-465b-8045-024475c079dd" - }, - "name": { - "type": "string", - "description": "Friendly name for this estimation" - }, - "task_id": { + "scan_id": { "type": "string", - "description": "Task identifier", - "example": "cost_est_owner-repo_12345" + "format": "uuid", + "description": "Stable pentest id; task_id changes when it starts" }, "status": { "type": "string", - "description": "Estimation status", "enum": [ "pending", "running", "completed", - "partial", - "failed" - ], - "example": "completed" + "failed", + "stopped", + "cancelled", + "skipped", + "pending_verification", + "pending_triage", + "draft", + "estimating", + "estimated" + ] }, "repos": { - "description": "Input repositories", "type": "array", "items": { - "$ref": "#/components/schemas/RepoInputResponseDto" + "$ref": "#/components/schemas/RepoDataDto" } }, - "total_credits": { - "type": "number", - "description": "Total estimated credits across all repos", - "example": 8000 + "target_urls": { + "type": "array", + "items": { + "type": "string" + } }, - "repo_results": { - "description": "Per-repo estimation results", + "login_targets": { "type": "array", "items": { - "$ref": "#/components/schemas/RepoResultResponseDto" + "$ref": "#/components/schemas/RestPentestLoginTargetDto" } }, - "error": { + "custom_context": { "type": "string", - "description": "Error message" - }, - "triage_required": { - "type": "boolean", - "description": "Whether human triage is mandatory for a scan of this estimation (large app, above the predicted-cost threshold). The customer cannot opt out when true.", - "example": false - }, - "triage_fee_credits": { - "type": "number", - "description": "Triage fee in credits (a percentage of the scan base credits) added when triage is enabled", - "example": 1600 + "nullable": true }, - "created_at": { - "format": "date-time", + "application_id": { "type": "string", - "description": "Timestamp when estimation was created", - "example": "2026-03-10T12:00:00.000Z" + "nullable": true, + "format": "uuid" }, - "updated_at": { - "format": "date-time", + "cost_estimation_id": { "type": "string", - "description": "Timestamp when estimation was last updated", - "example": "2026-03-10T12:05:00.000Z" + "nullable": true, + "format": "uuid" + }, + "triage_enabled": { + "type": "boolean", + "nullable": true + }, + "credits_used": { + "type": "number", + "nullable": true } }, "required": [ - "id", - "organization_id", - "user_id", - "task_id", + "scan_id", "status", "repos", - "triage_required", - "triage_fee_credits", - "created_at", - "updated_at" + "target_urls", + "login_targets", + "custom_context", + "application_id", + "cost_estimation_id", + "triage_enabled", + "credits_used" ] }, - "RestCostEstimationListResponseDto": { + "RestUpdatePentestDto": { "type": "object", "properties": { - "data": { + "target_urls": { + "description": "Target URLs (pentest only)", "type": "array", "items": { - "$ref": "#/components/schemas/CostEstimationResponseDto" + "type": "string" } }, - "total": { - "type": "number" + "custom_context": { + "type": "string", + "description": "Custom context (pentest only)" + }, + "application_id": { + "type": "string", + "description": "iva application this scan targets (group of repos)" + }, + "login_targets": { + "description": "Deployed sites to test signed in, each with how to sign in. On update the list replaces the saved one, and [] removes them. A pentest tests live sites either all signed in or all without a login (target_urls). Check each login with POST /pentests/{id}/login-checks before starting.", + "maxItems": 10, + "type": "array", + "items": { + "$ref": "#/components/schemas/RestLoginTargetDto" + } + }, + "repos": { + "type": "array", + "description": "Repositories or uploaded archives to test, replacing the saved ones. Changing them clears the estimate, so estimate again before starting.", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/ConnectedRepoInputDto" + }, + { + "$ref": "#/components/schemas/PublicRepoInputDto" + }, + { + "$ref": "#/components/schemas/UploadRepoInputDto" + } + ], + "discriminator": { + "propertyName": "source", + "mapping": { + "connected": "#/components/schemas/ConnectedRepoInputDto", + "public": "#/components/schemas/PublicRepoInputDto", + "upload": "#/components/schemas/UploadRepoInputDto" + } + } + }, + "minItems": 1 + } + } + }, + "RestStartPentestDto": { + "type": "object", + "properties": { + "expected_credits": { + "type": "integer", + "description": "The total the user confirmed: the estimate's total_credits, plus triage_fee_credits when triage is required or opted in", + "minimum": 1 + }, + "triage_enabled": { + "type": "boolean", + "description": "Opt in to human triage of findings when it is not already required" } }, "required": [ - "data", - "total" + "expected_credits" ] }, - "UnifiedRepoDto": { + "RestDiscardPentestResponseDto": { "type": "object", "properties": { - "id": { + "scan_id": { "type": "string", - "description": "Internal UUID", - "example": "123e4567-e89b-12d3-a456-426614174000" + "format": "uuid" }, - "provider": { + "discarded": { + "type": "boolean" + } + }, + "required": [ + "scan_id", + "discarded" + ] + }, + "RequestUploadDto": { + "type": "object", + "properties": { + "file_name": { "type": "string", - "description": "Source provider", - "enum": [ - "github", - "gitlab", - "bitbucket", - "azure" - ], - "example": "github" + "description": "Original filename (e.g. \"my-project.zip\")", + "example": "my-project.zip" }, "name": { "type": "string", - "description": "Display name (owner/repo for GitHub, namespace/project for GitLab, workspace/slug for Bitbucket, project/repository for Azure DevOps)", - "example": "octocat/Hello-World" - }, - "providerKey": { + "description": "Display name (defaults to filename)" + } + }, + "required": [ + "file_name" + ] + }, + "RequestUploadResponseDto": { + "type": "object", + "properties": { + "id": { "type": "string", - "description": "Provider-native identifier for mutation endpoints. GitHub: owner/repo, GitLab: numeric projectId (as string), Bitbucket: repository UUID, Azure DevOps: repository UUID.", - "example": "octocat/Hello-World" + "description": "Archive ID" }, - "enabled": { - "type": "boolean", - "description": "Whether the repository is enabled for scanning", - "example": true + "upload_url": { + "type": "string", + "description": "Signed GCS upload URL (PUT)" }, - "threatModelUpdatedAt": { + "content_type": { "type": "string", - "nullable": true, - "description": "ISO timestamp of the repo's most recent threat_model.md update, or null if it has none. Repos are returned ordered by this (most recent first; repos without a threat model last).", - "example": "2026-06-22T12:00:00.000Z" + "description": "Content-Type to use in the PUT request", + "example": "application/zip" + } + }, + "required": [ + "id", + "upload_url", + "content_type" + ] + }, + "PentestCreditBalanceResponseDto": { + "type": "object", + "properties": { + "organization_id": { + "type": "string", + "description": "Organization UUID", + "example": "f336d0bc-b841-465b-8045-024475c079dd" }, - "openFindingsCount": { + "balance": { "type": "number", - "description": "Number of approved, visible findings that remain open for this repository", - "example": 3 - }, - "lastScanAt": { - "type": "string", - "nullable": true, - "description": "ISO timestamp of the most recent completed scan containing this repository, or null if it has not been scanned", - "example": "2026-09-15T12:00:00.000Z" + "description": "Current pentest credit balance available for use", + "example": 12000 }, - "ossApproved": { - "type": "boolean", - "description": "Whether this github.com repository is approved for free OSS PR reviews. Independent of whether PR scans are enabled.", - "example": true + "total_purchased": { + "type": "number", + "description": "Total pentest credits purchased across all time", + "example": 16000 }, - "prScansEnabled": { - "type": "boolean", - "description": "Whether PR/MR scans are enabled (GitHub and GitLab)", - "example": true + "total_used": { + "type": "number", + "description": "Total pentest credits deducted for completed/running pentests", + "example": 8000 }, - "prScanBranches": { - "description": "Target branch patterns for PR/MR scans (GitHub and GitLab)", - "example": [ - "main", - "**/release/**" - ], - "type": "array", - "items": { - "type": "string" - } + "total_refunded": { + "type": "number", + "description": "Total pentest credits refunded from cancelled pentests", + "example": 4000 }, - "installationId": { + "total_granted": { "type": "number", - "description": "GitHub App installation ID (GitHub only)", - "example": 12345678 + "description": "Total pentest credits granted at no charge", + "example": 2000 }, - "baseUrl": { - "type": "string", + "used_this_period": { + "type": "number", "nullable": true, - "description": "GitHub Enterprise base URL for self-hosted (PAT) installations (null/absent for github.com), the GitLab installation URL, or the Azure DevOps organization URL. Clients should build clone/display URLs from this host instead of assuming the public provider host.", - "example": "https://ghe.example.com" - }, - "defaultBranch": { - "type": "string", - "description": "Default branch (GitLab only)", - "example": "main" - }, - "visibility": { - "type": "string", - "description": "Project visibility (GitLab only)", - "example": "private" - }, - "webhookRegistered": { - "type": "boolean", - "description": "Whether a webhook is registered for this project (GitLab only)", - "example": true + "description": "Credits deducted since the start of the current billing period, null when the organization has no billing period", + "example": 1240 }, - "isPrivate": { + "is_unlimited": { "type": "boolean", - "description": "Whether the repository is private (Bitbucket only)", - "example": true + "description": "Whether this organization has internal unlimited pentest entitlement", + "example": false } }, "required": [ - "id", - "provider", - "name", - "providerKey", - "enabled", - "threatModelUpdatedAt", - "openFindingsCount", - "lastScanAt" + "organization_id", + "balance", + "total_purchased", + "total_used", + "total_refunded", + "total_granted", + "used_this_period", + "is_unlimited" ] }, - "MergedReposResponseDto": { + "RepositoryBatchResultDto": { "type": "object", "properties": { - "repos": { - "description": "Unified list of repositories from all providers", + "processed": { + "type": "number", + "description": "Keys applied", + "example": 2 + }, + "failed": { + "type": "number", + "description": "Keys that failed", + "example": 0 + }, + "failedRepositories": { + "description": "Keys that failed, with the reason", "type": "array", "items": { - "$ref": "#/components/schemas/UnifiedRepoDto" + "$ref": "#/components/schemas/RepositoryBatchFailureDto" } }, - "errors": { - "description": "Providers that failed to respond", + "webhookPending": { + "description": "Keys turned on whose provider webhook could not be registered", "example": [], "type": "array", "items": { @@ -2328,173 +3760,180 @@ } }, "required": [ - "repos", - "errors" + "processed", + "failed", + "failedRepositories", + "webhookPending" ] }, - "RestHfwBatchPackageDto": { + "RepositoryBatchFailureDto": { "type": "object", "properties": { - "ecosystem": { + "repo": { "type": "string", - "enum": [ - "npm", - "pypi" - ] - }, - "package": { - "type": "string" + "description": "The key the request named", + "example": "acme/api" }, - "version": { - "type": "string" + "reason": { + "type": "string", + "description": "Why the key was not applied", + "example": "Repository acme/api not found" } }, "required": [ - "ecosystem", - "package", - "version" + "repo", + "reason" ] }, - "RestHfwBatchRequestDto": { + "RepositoryIdBatchDto": { "type": "object", "properties": { - "packages": { + "enable": { + "description": "Ids from the repository list of the repositories to turn on", + "example": [ + "123e4567-e89b-12d3-a456-426614174000" + ], "type": "array", "items": { - "$ref": "#/components/schemas/RestHfwBatchPackageDto" + "type": "string", + "format": "uuid" + } + }, + "disable": { + "description": "Ids from the repository list of the repositories to turn off", + "example": [], + "type": "array", + "items": { + "type": "string", + "format": "uuid" } } }, "required": [ - "packages" + "enable", + "disable" ] }, - "RestHfwVerdictResponseDto": { + "RestPrReviewSettingsDto": { "type": "object", "properties": { - "status": { - "type": "string", - "enum": [ - "ready", - "pending", - "scanning" - ] - }, - "ecosystem": { - "type": "string", - "enum": [ - "npm", - "pypi" - ] - }, - "package": { - "type": "string" + "scm_comments_enabled": { + "type": "boolean", + "description": "Whether Hacktron publishes comments and finding details to connected source control pull and merge requests" }, - "version": { - "type": "string" + "scm_system_comments_enabled": { + "type": "boolean", + "description": "Whether Hacktron posts system comments such as skipped reviews, usage limits, and configuration warnings. Defaults to true. Requires scm_comments_enabled." }, - "level": { + "fail_on_severity": { "type": "string", + "description": "Org-wide default fail_on severity threshold for PR/MR checks. A repo's .hacktron/config.yaml fail_on.severity overrides this. Send null to disable.", "enum": [ - "clean", - "suspicious", - "malicious", - "unknown" - ] - }, - "confidence": { - "type": "string" - }, - "summary": { - "type": "string" + "critical", + "high", + "medium", + "low", + "info" + ], + "nullable": true }, - "findings": { + "scan_include_branches": { + "description": "Org-wide default target branch patterns to scan PRs/MRs for (null = all branches). Glob patterns (`*`, `**`, `?`, `{a,b}`), case-insensitive. A repo's .hacktron/config.yaml include.branches overrides this.", + "example": [ + "main", + "**/release/**" + ], + "nullable": true, "type": "array", "items": { - "type": "object" + "type": "string" } }, - "source": { - "type": "string" + "scan_exclude_branches": { + "description": "Org-wide default target branch patterns to exclude from PR/MR scans (null = none). Takes precedence over scan_include_branches. A repo's .hacktron/config.yaml exclude.branches overrides this.", + "example": [ + "**/release/legacy/**" + ], + "nullable": true, + "type": "array", + "items": { + "type": "string" + } }, - "osv_id": { - "type": "string" - } - }, - "required": [ - "status", - "ecosystem", - "package", - "version", - "level", - "confidence", - "summary", - "findings" - ] - }, - "RestHfwBatchResponseDto": { - "type": "object", - "properties": { - "status": { - "type": "string" + "scan_include_labels": { + "description": "Org-wide default labels to require on PRs/MRs for scanning (null = any). A repo's .hacktron/config.yaml include.labels overrides this.", + "example": [ + "feature", + "bugfix" + ], + "nullable": true, + "type": "array", + "items": { + "type": "string" + } }, - "verdicts": { + "scan_exclude_labels": { + "description": "Org-wide default labels to exclude PR/MR scans for (null = none). Takes precedence over scan_include_labels. A repo's .hacktron/config.yaml exclude.labels overrides this.", + "example": [ + "wip", + "no-scan" + ], + "nullable": true, "type": "array", "items": { - "$ref": "#/components/schemas/RestHfwVerdictResponseDto" + "type": "string" } }, - "errors": { + "scan_include_authors": { + "description": "Org-wide default author patterns to require on PRs/MRs for scanning (null = any). Glob patterns (`*`, `**`, `?`, `{a,b}`), case-insensitive. A repo's .hacktron/config.yaml include.authors overrides this.", + "example": [ + "alice", + "bob" + ], + "nullable": true, "type": "array", "items": { - "type": "object" + "type": "string" } - } - }, - "required": [ - "status", - "verdicts", - "errors" - ] - }, - "RestHfwOsvFeedResultDto": { - "type": "object", - "properties": { - "ecosystem": { - "type": "string" }, - "package": { - "type": "string" + "scan_exclude_authors": { + "description": "Org-wide default author patterns to exclude PR/MR scans for (null = none). Glob patterns (`*`, `**`, `?`, `{a,b}`), case-insensitive. Takes precedence over scan_include_authors. A repo's .hacktron/config.yaml exclude.authors overrides this.", + "example": [ + "dependabot[bot]", + "renovate[bot]", + "*-bot" + ], + "nullable": true, + "type": "array", + "items": { + "type": "string" + } }, - "versions": { + "scan_include_keywords": { + "description": "Org-wide default title keywords to require on PRs/MRs for scanning (null = any). Case-insensitive substring match. A repo's .hacktron/config.yaml include.keywords overrides this.", + "example": [ + "release", + "security" + ], + "nullable": true, "type": "array", "items": { "type": "string" } - } - }, - "required": [ - "ecosystem", - "package", - "versions" - ] - }, - "RestHfwOsvFeedResponseDto": { - "type": "object", - "properties": { - "total": { - "type": "number" }, - "results": { + "scan_exclude_keywords": { + "description": "Org-wide default title keywords to exclude PR/MR scans for (null = none). Case-insensitive substring match. A repo's .hacktron/config.yaml exclude.keywords overrides this.", + "example": [ + "[exclude hacktron]", + "wip" + ], + "nullable": true, "type": "array", "items": { - "$ref": "#/components/schemas/RestHfwOsvFeedResultDto" + "type": "string" } } - }, - "required": [ - "results" - ] + } } } }, @@ -2503,4 +3942,4 @@ "api-key": [] } ] -} \ No newline at end of file +} diff --git a/platform/communication-apps/slack.mdx b/platform/communication-apps/slack.mdx index a0e1012..a43ab37 100644 --- a/platform/communication-apps/slack.mdx +++ b/platform/communication-apps/slack.mdx @@ -12,15 +12,16 @@ You also need permission to authorize apps in the Slack workspace you want Hackt ## Configure Slack - + In Hacktron, go to **Integrations** and find **Slack** under - **Communication Apps**. + **Notifications**. - Slack integration card in Hacktron + Slack under Notifications in Integrations, with a Connect button - - Click **Connect**. Your browser will redirect to Slack. + + Click **Connect**, then **Connect Slack** in the panel that opens. + Your browser redirects to Slack. Sign in to Slack if required, choose the workspace, and approve the @@ -28,7 +29,8 @@ You also need permission to authorize apps in the Slack workspace you want Hackt Hacktron uses this access to send security notifications and scan updates - to the Slack channel you configure. + to your configured channel. It also lets people work with Hacktron by + mentioning @Hacktron AI. @@ -36,27 +38,34 @@ You also need permission to authorize apps in the Slack workspace you want Hackt After authorization, Hacktron redirects you back to **Integrations**. - Click the settings icon on the Slack card. + Click **Manage** next to Slack to open its panel. Select the Slack channel that should receive Hacktron scan updates. The setting saves when you choose a different channel. - Slack channel configuration - ## Updating the channel -Open the Slack settings panel again and choose a different channel. Hacktron removes the previous channel configuration and saves the new one. +Open the Slack panel again and choose a different channel. Hacktron saves the new destination for notifications. ## Disconnecting Slack -Open the Slack card in **Integrations** and choose **Disconnect**. Hacktron will stop sending notifications and alerts to Slack. +In **Integrations**, click **Manage** next to Slack, then **Disconnect Slack**. Confirm the disconnection to stop notifications and alerts. ## Common checks -- If Slack does not connect, retry from **Integrations** so Hacktron can create a fresh OAuth state. +- If Slack does not connect, retry from **Integrations**. - If a channel is missing, confirm that the Slack workspace account used for OAuth can access that channel. - If notifications are not appearing, confirm that the Slack integration is connected and a channel is selected. + +## Next: use Hacktron in Slack + +Connecting Slack installs the Hacktron AI app in your workspace. Add it to a channel with `/invite @Hacktron AI`. Each person then links their Slack and Hacktron accounts to use the assistant with their own permissions. + + + Triage findings, set up pentests and manage PR reviews by mentioning @Hacktron + AI. + diff --git a/platform/slack-assistant.mdx b/platform/slack-assistant.mdx new file mode 100644 index 0000000..71004c6 --- /dev/null +++ b/platform/slack-assistant.mdx @@ -0,0 +1,156 @@ +--- +title: "Hacktron in Slack" +description: "Mention @Hacktron AI to triage findings, set up pentests and change PR review settings without leaving Slack." +--- + +Mention **@Hacktron AI** in a Slack channel or direct message to work with your findings, set up a pentest or manage PR reviews. The assistant uses your Hacktron account and permissions, and replies in a thread. + +You can continue in that thread without mentioning it again. Teammates can join by mentioning @Hacktron AI. Each person's requests use their own account and permissions. + +## What you can ask it to do + +| Task | Example request | +| ------------------------------------------------- | ------------------------------------------------------------------------------------ | +| [Look into findings](#ask-about-findings) | How many critical findings are open in acme/api? | +| [Triage a finding](#act-on-findings-and-scans) | Mark the SSRF finding in acme/api as a false positive because the URL is allowlisted | +| [Set up a pentest](#set-up-a-pentest) | Pentest acme/api on main and test staging.acme.com signed in | +| [Manage PR reviews](#manage-pr-reviews) | Turn on PR reviews for acme/api and acme/web | +| [Ask how Hacktron works](#ask-how-hacktron-works) | How do I stop reviews of PRs into release branches? | + +Before changing a finding, stopping a scan or updating PR review settings, Hacktron shows you what will change and asks you to click **Approve**. Starting a pentest requires approval from an organization admin. + +## Set up + + + + In Hacktron, go to **Integrations** and find **Slack** under + **Notifications**. Click **Connect**, then **Connect Slack** in the panel + that opens. Approve the Hacktron AI app for your workspace to install it. + See + [Slack](/platform/communication-apps/slack) for the full steps. + + Slack under Notifications in Integrations, with a Connect button + + + If Slack is already connected but Hacktron can't read attached archives, + disconnect Slack and connect it again from **Integrations** to grant + file access. + + + + + In Hacktron, open **Personal settings**, find **Connected accounts** and click + **Connect** next to **Slack**. Choose the workspace connected to your + organization so Hacktron can match your Slack and Hacktron accounts. See [Link your Slack + identity](/code-review/integrations/slack#link-your-slack-identity). + + Connected accounts in Personal settings, with a Connect button next to Slack + + + + In Slack, open the channel and send `/invite @Hacktron AI`. You can also + click the channel name, open **Integrations** and click **Add apps**. This + works for public and private channels. + + To send a direct message, find **Hacktron AI** under **Apps** in + the Slack sidebar and send it a message. + + + + Mention **@Hacktron AI** with a request. The first time, Hacktron sends + you a private **Connect Hacktron** message. Click the button, sign in to + Hacktron and allow access. Hacktron waits for you to connect, then + continues with your request in the thread. + + + +Your Hacktron role determines what you can do. Viewers can ask questions, members can prepare pentests, and organization admins can start pentests and change PR review settings. + +## Ask about findings + +Ask in your own words, for example: + +- "How many critical findings are open in acme/api?" +- "Show me the high severity findings from the last pentest" +- "Which repositories have the most open findings?" + +Hacktron can return counts or post finding cards with links to the details in Hacktron. + +## Act on findings and scans + +Ask Hacktron to change a finding's status, add a comment, stop a running scan or discard a pentest draft. Review the proposed change and click **Approve**. Hacktron confirms the result in the thread. + +Each finding card also has buttons to update its status or fix it with AI. + +## Ask how Hacktron works + +Ask a product question, such as "How do I stop reviews of PRs into release branches?" Hacktron answers from the documentation and links to the relevant page. If the docs don't cover your question, it tells you. + +## Manage PR reviews + +Ask Hacktron to turn PR reviews on or off for a repository. You can also change organization settings, including whether reviews post comments, which finding severity fails the PR check, and filters for target branches, labels, authors and title keywords. + +Anyone can ask to see the settings. Only organization admins can change them, after reviewing the change and clicking **Approve**. + +## Set up a pentest + + + + Name a connected repository, or attach a `.zip`, `.7z` or `.tar.gz` archive + of the code (up to 2 GB) to your message. + + + To test part of the code, name the folders in the thread, for example "only + the services/payments folder". You can keep the same archive. + + + Provide a deployed app URL if you want live testing alongside the code + review. Use a staging environment where possible. Without a URL, the pentest + reviews the code only. + + + Hacktron estimates the pentest and asks you to confirm. + + + Hacktron posts an approval card. An organization admin clicks **Start + pentest**. Credits are spent when the pentest starts. + + + When the pentest finishes, Hacktron posts the results in the same thread. + + + +### Test a site signed in + +If the live site requires a login, click **Add login details** to open a private form. The form sends your details directly to Hacktron for encrypted storage. It keeps them out of the Slack thread and the assistant's conversation. Use this form rather than pasting credentials into Slack. + +Hacktron then checks that it can sign in and posts the result in the thread. If the check fails, click **Update login details** to fix them. + +A pentest uses the same access mode for all its live sites: signed in or without a login. To test both, create separate pentests. + + + Signed-in testing must be enabled for your organization. If it isn't + available, Hacktron lets you know. You can still test without signing in. + + +## Follow what it's doing + +A progress card at the top of the reply shows Hacktron's plan and the steps it takes. + +## Limits + +- The Slack assistant can read attached archives, but it doesn't build or run your code. Provide a deployed app URL for live testing. +- Each thread has a usage limit. When a thread reaches it, Hacktron stops and asks you to mention it in a new thread. +- If Hacktron can't read an attachment, ask an organization admin to disconnect Slack and connect it again from **Integrations** to grant file access. + +## Troubleshooting + +- **Hacktron doesn't reply to a mention in a channel.** Add it to the channel with `/invite @Hacktron AI`, and check that Slack is connected under **Integrations** in Hacktron. +- **"Hacktron couldn't find your account for this workspace."** Link your Slack account from **Personal settings** > **Connected accounts** in Hacktron, choosing the same workspace, then try again. +- **Hacktron doesn't answer a reply in a thread.** If someone else started the thread, mention **@Hacktron AI** in your reply. Only the person who started it can continue without a mention. diff --git a/white-box-pentest/quickstart.mdx b/white-box-pentest/quickstart.mdx index 49094ae..3690b04 100644 --- a/white-box-pentest/quickstart.mdx +++ b/white-box-pentest/quickstart.mdx @@ -197,28 +197,33 @@ For a public target: | Exclusions | Systems, data, tenants, destructive actions, or rate limits that are out of scope. | | Context documents | Architecture notes, threat models, API specs, or prior pentest reports. | -## API option +## Other ways to start a pentest -You can also start Whitebox Scans from the REST API. The API follows the -same structure as the dashboard flow: create a cost estimation first, wait for a -completed or partial estimate with credits, then start the scan with the same -repositories, branches, `include_paths`, and `exclude_paths`. Create a new -estimate if you change the scope. +You can also prepare a pentest in Slack, through an AI assistant connected to +Hacktron's MCP server, or through the REST API. Set the scope and review the +credit estimate before starting. These options require an organization admin +to start the pentest. + + Mention @Hacktron AI with a repository or an attached archive. + + + Set up pentests from Claude, Cursor, and other AI assistants. + - Estimate the credit cost for one or more repositories. + Create a draft with repositories or archives, and optional live sites. - Start a Whitebox Scan after the cost estimate is ready. + Start a code-only scan from a completed cost estimation.