@cantinasecurity/apex-cli 0.1.37 → 0.1.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/apex-cli/SKILL.md +18 -3
- package/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.mcp.claude.json +1 -1
- package/.mcp.codex.json +1 -1
- package/MARKETPLACE.md +1 -1
- package/README.md +59 -18
- package/dist/apex.js +9 -3
- package/dist/args.js +2 -0
- package/dist/commands.js +47 -14
- package/dist/findings.js +6 -1
- package/dist/forgejo-url.js +53 -0
- package/dist/help.js +12 -4
- package/dist/mcp.js +23 -6
- package/dist/repo-discovery.js +5 -5
- package/dist/repo-url.js +10 -0
- package/dist/shell.js +11 -5
- package/dist/telemetry.js +5 -4
- package/dist/types.js +4 -1
- package/package.json +1 -1
- package/skills/apex-cli/SKILL.md +19 -4
|
@@ -13,7 +13,7 @@ This skill is bundled with Apex CLI and can be installed into the current reposi
|
|
|
13
13
|
|
|
14
14
|
Use Apex through the MCP server when the `apex-*` tools are available. The tools installed by `apex setup` use the local stdio server and device login.
|
|
15
15
|
|
|
16
|
-
For a production MCP client that does not need local filesystem access, use the hosted Streamable HTTP endpoint at `https://ai.cantina.xyz/mcp`. Read the organization service key from `APEX_SERVICE_KEY` and supply it only as `Authorization: Bearer <TOKEN>`; never put the raw key in a tool argument, result, prompt, source file, or log. The hosted tools are `apex-credits`, `apex-workspaces`, `apex-scan`, `apex-black-start`, `apex-status`, `apex-cancel-scan`, `apex-findings`, `apex-workspace-findings`, `apex-finding-tickets`, `apex-finding-link-ticket`, and `apex-finding-
|
|
16
|
+
For a production MCP client that does not need local filesystem access, use the hosted Streamable HTTP endpoint at `https://ai.cantina.xyz/mcp`. Read the organization service key from `APEX_SERVICE_KEY` and supply it only as `Authorization: Bearer <TOKEN>`; never put the raw key in a tool argument, result, prompt, source file, or log. The hosted tools are `apex-credits`, `apex-workspaces`, `apex-scan`, `apex-black-start`, `apex-status`, `apex-cancel-scan`, `apex-findings`, `apex-workspace-findings`, `apex-finding-tickets`, `apex-finding-link-ticket`, `apex-finding-send`, and `apex-finding-status`.
|
|
17
17
|
|
|
18
18
|
OAuth-capable clients may instead use the hosted endpoint’s OAuth discovery and Cantina manager-consent flow. Let the client manage OAuth credentials; never pass them to tools, the REST API, or local CLI. Local stdio MCP continues to use device login.
|
|
19
19
|
|
|
@@ -76,7 +76,7 @@ If the Apex MCP server is not configured, fall back to the local CLI:
|
|
|
76
76
|
- `apex scan` scans the current working directory by default; pass `--repo` only when the user asks to scan explicit alternate roots.
|
|
77
77
|
- `apex-doctor` is read-only for guest sessions and reports the local snapshot plan without reserving a workspace. For account sessions it reports whether Apex will use remote materialization or a local snapshot upload for each selected source.
|
|
78
78
|
- Plain local directories and dirty git worktrees can scan through local snapshot uploads without provider access.
|
|
79
|
-
- Audit scans use `--mode audit` in user-facing CLI calls and request payloads. The legacy `ultra` mode remains accepted as an alias, but audit scans still require provider-backed GitHub or
|
|
79
|
+
- Audit scans use `--mode audit` in user-facing CLI calls and request payloads. The legacy `ultra` mode remains accepted as an alias, but audit scans still require provider-backed GitHub, GitLab or company-enabled Forgejo sources.
|
|
80
80
|
- Lite scans use `--mode lite` in CLI calls or `mode: "lite"` in MCP, require Lite Scan beta access, and require provider-backed GitHub or GitLab sources without local snapshot fallback.
|
|
81
81
|
- PR scans use `--mode pr --pr <number>` in CLI calls, or `mode: "pr"` with `pullRequests` in MCP. They require one provider-backed GitHub repository.
|
|
82
82
|
- Starting a PR scan may run alongside active workspace scans and does not require `--force` or `force: true`. Starting any non-PR scan still treats every active scan, including a PR scan, as a blocker; use force only when the user explicitly wants that overlap.
|
|
@@ -155,8 +155,23 @@ values. If Audit Setup or another workspace update is active, wait for it to
|
|
|
155
155
|
finish before saving again.
|
|
156
156
|
|
|
157
157
|
Scan target and recovery rules:
|
|
158
|
-
- CLI `apex scan [target...]` and repeatable `--repo` accept exact files, directories, or HTTPS GitHub/GitLab repository URLs; local MCP uses `repoPaths` for the same selections. No target means `cwd`.
|
|
158
|
+
- CLI `apex scan [target...]` and repeatable `--repo` accept exact files, directories, or HTTPS GitHub/GitLab or company-enabled Forgejo repository URLs; local MCP uses `repoPaths` for the same selections. No target means `cwd`.
|
|
159
159
|
- A file selection scans only that file, respecting ignore rules. Local snapshots exclude `.apex/` state. Remote URLs use the default branch and cannot fall back to uploading `cwd`; clone locally to choose a branch or commit.
|
|
160
160
|
- Use Standard for local changes, files, plain directories, and public repositories without provider access. Lite and Audit still require connected provider sources; do not silently substitute another mode.
|
|
161
161
|
- Unauthenticated headless CLI commands fail with explicit login guidance; they never start device login. Use the MCP auth tools for explicit MCP login. Retry transient refresh failures without clearing credentials.
|
|
162
162
|
- Audit Setup is an explicit Standard/Audit beta flow using saved workspace repositories and context. It does not preserve local targets. Never automatically substitute setup after `AUDIT_SETUP_REQUIRED`; use the error's selected-workspace command only when that saved scope is intended. Draft operations require the ID returned by preparation; eligible accounts receive setup commands in CLI help. Lite and PR do not use `--setup`.
|
|
163
|
+
|
|
164
|
+
Finding lifecycle:
|
|
165
|
+
- Close resolved issues with `apex-finding-status` and `status: "fixed"`; reopen with `status: "open"`.
|
|
166
|
+
- Local MCP takes `findingRef`, optional `cwd`/`scanId`, `note`, and `expectedUpdatedAt`, using device login. Hosted MCP takes `findingId` (UUID), no local paths, and requires explicit `findings:write`.
|
|
167
|
+
- The scripted equivalent is `apex findings status <finding-ref> <open|fixed> --note "rationale" --json`. Other statuses: acknowledged, in_review, pending_merge, false_positive, wont_fix, skipped.
|
|
168
|
+
- Status changes preserve scanner validity, severity, feedback, and linked ticket state. Invalid or duplicate findings cannot be reopened into the open queue until their classification is reviewed.
|
|
169
|
+
- Read `remediationStatus` in workspace finding lists for the current lifecycle; `status` remains the scanner verdict. `open` and `acknowledged` stay distinct even though both use the `accepted` review state.
|
|
170
|
+
- Use the finding updatedAt as expectedUpdatedAt when working from a prior snapshot. On FINDING_STATUS_CONFLICT, reload and review before retrying.
|
|
171
|
+
- Linear sync is Linear-to-Apex: started becomes acknowledged, completed becomes fixed. Do not promise Apex-to-Linear status updates or automatic Linear reopening.
|
|
172
|
+
|
|
173
|
+
## Forgejo
|
|
174
|
+
|
|
175
|
+
Forgejo connections, remote repository scans, PR scans and Fix Review require the Forgejo company feature enabled by an administrator in Bedrock Settings. Run `apex connect forgejo` (or `apex-connect-provider` with `provider: "forgejo"`) to obtain the browser connection URL. A dedicated bot, HTTPS instance URL and repository-scoped read token are required; PR statuses, comments and webhook installation also require a write token.
|
|
176
|
+
|
|
177
|
+
After connecting, run `apex doctor --repo . --source-mode remote`, then `apex scan --mode standard --repo . --source-mode remote` or `apex scan --mode pr --pr 42 --repo .`. Same-repository Forgejo PR URLs use `/owner/repository/pulls/42`. Pass a Forgejo Fix PR URL through finding feedback and start Fix Review using the existing finding commands or MCP tools. Disabling the company flag stops native Forgejo access. Existing local Standard snapshots remain provider-independent.
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Cantina agent plugins for security review workflows.",
|
|
9
|
-
"version": "0.1.
|
|
9
|
+
"version": "0.1.39"
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
"source": {
|
|
15
15
|
"source": "npm",
|
|
16
16
|
"package": "@cantinasecurity/apex-cli",
|
|
17
|
-
"version": "0.1.
|
|
17
|
+
"version": "0.1.39"
|
|
18
18
|
},
|
|
19
19
|
"description": "Run Apex security scans and review findings from Claude Code.",
|
|
20
|
-
"version": "0.1.
|
|
20
|
+
"version": "0.1.39",
|
|
21
21
|
"author": {
|
|
22
22
|
"name": "Cantina",
|
|
23
23
|
"email": "support@cantina.xyz"
|
package/.mcp.claude.json
CHANGED
package/.mcp.codex.json
CHANGED
package/MARKETPLACE.md
CHANGED
|
@@ -13,7 +13,7 @@ This package is prepared as both a Claude Code plugin and a Codex plugin. The pu
|
|
|
13
13
|
The plugin MCP configs launch the pinned npm CLI package with:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
npx -y -p @cantinasecurity/apex-cli@0.1.
|
|
16
|
+
npx -y -p @cantinasecurity/apex-cli@0.1.39 apex-mcp
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
That keeps marketplace installs independent of a user's global `apex` install.
|
package/README.md
CHANGED
|
@@ -158,30 +158,30 @@ unclaimed local trial proof so `apex claim` can still use it.
|
|
|
158
158
|
Agent-safe copy/paste prompt (substitute the issued code):
|
|
159
159
|
|
|
160
160
|
```text
|
|
161
|
-
Install and run Cantina Apex CLI 0.1.
|
|
161
|
+
Install and run Cantina Apex CLI 0.1.39 to start one free standard security scan of this repository immediately.
|
|
162
162
|
|
|
163
163
|
Use this trial code only where <TRIAL_CODE> appears below: SPBT-DEVREL-XXXXXXXX
|
|
164
164
|
|
|
165
165
|
Run the commands yourself from the repository root; do not stop after setup or only explain the steps. Use terminal CLI commands only. Do not use an Apex MCP tool or run `apex setup`, `apex mcp`, `apex login`, `apex-auth-start`, or `apex-auth-wait`. Never use `--replace` or `--force`.
|
|
166
166
|
|
|
167
167
|
Use this exact command prefix for every Apex command:
|
|
168
|
-
npx -y -p @cantinasecurity/apex-cli@0.1.
|
|
168
|
+
npx -y -p @cantinasecurity/apex-cli@0.1.39 apex
|
|
169
169
|
|
|
170
170
|
1. Before redeeming, confirm every command will use the same persistent, user-accessible machine or container, HOME, filesystem, and repository root. Confirm Node.js 20+ and npx are available. If `APEX_BASE_URL` is set, continue only when its origin is `https://ai.cantina.xyz`. Run the exact prefix with `--help` and continue only if help lists both `redeem` and `claim`. If any check fails, STOP before sending the code; do not install system packages.
|
|
171
171
|
2. Redeem, with at most one response-loss recovery retry:
|
|
172
|
-
npx -y -p @cantinasecurity/apex-cli@0.1.
|
|
172
|
+
npx -y -p @cantinasecurity/apex-cli@0.1.39 apex redeem <TRIAL_CODE> --json
|
|
173
173
|
This terminal command is the only allowed tool call containing the code. Never echo or restate the code, store it in a file or environment variable, put it in a URL or MCP call, or include it in your response. Parse the JSON from stdout only; do not merge stderr into stdout, because npm may write warnings to stderr. Continue when the command exits 0 and stdout is JSON with `redeemed: true` and `outcome` equal to `redeemed` or `idempotent`. `idempotent` means the existing guest session resumed and it may already own a workspace. If and only if the first attempt ends with an ambiguous transport/connection failure or an interrupted/missing response and no typed server error code, retry this exact same command once on the same machine, HOME, and repository root. Never retry a typed server rejection and never make more than one recovery retry. If the allowed retry does not return a successful JSON outcome, STOP.
|
|
174
174
|
3. Immediately start exactly one scan; do not wait for another instruction:
|
|
175
|
-
npx -y -p @cantinasecurity/apex-cli@0.1.
|
|
175
|
+
npx -y -p @cantinasecurity/apex-cli@0.1.39 apex scan --mode standard --non-interactive --no-open --json
|
|
176
176
|
The CLI validates the complete guest local archive before it reserves a workspace. An unbound renamed checkout may recover only the sole empty guest workspace when no explicit workspace name was supplied. On any error or nonzero exit, STOP. After a successful scan JSON response, inspect `localPersistence.workspaceBindingSaved`. If it is `false`, the server scan already started: capture and report `binding.lastScanId`, then STOP; do not run status or findings until local repository permissions are repaired. Otherwise capture `binding.lastScanId` from the JSON as <SCAN_ID>; if it is absent, STOP.
|
|
177
177
|
4. About every 60 seconds, poll that exact scan:
|
|
178
|
-
npx -y -p @cantinasecurity/apex-cli@0.1.
|
|
178
|
+
npx -y -p @cantinasecurity/apex-cli@0.1.39 apex status --scan <SCAN_ID> --json
|
|
179
179
|
Read `progress.progress.status`. Wait while it is `created`, `pending`, `queued`, `starting`, `running`, `in_progress`, or `processing`. Continue on `completed`. STOP on `failed`, `cancelled`, a missing or unknown status, or a command failure.
|
|
180
180
|
5. Fetch that exact scan's findings:
|
|
181
|
-
npx -y -p @cantinasecurity/apex-cli@0.1.
|
|
181
|
+
npx -y -p @cantinasecurity/apex-cli@0.1.39 apex findings --scan <SCAN_ID> --json
|
|
182
182
|
On success, summarize findings by severity, most severe first, including identifier, title, status, and confidence. This command does not return file locations, so do not invent them. On failure, STOP.
|
|
183
183
|
6. Do not claim automatically. Tell me the claim deadline from step 2 and remind me to run this exact command myself from the same repository root, machine, and HOME:
|
|
184
|
-
npx -y -p @cantinasecurity/apex-cli@0.1.
|
|
184
|
+
npx -y -p @cantinasecurity/apex-cli@0.1.39 apex claim
|
|
185
185
|
It may open a browser so I can create or sign in to a Cantina account and keep the results. Claim moves the trial into the currently selected Cantina workspace when I can run scans there, or into my existing personal workspace. If the selected workspace cannot run scans, tell me to switch to one that can and retry before the claim deadline.
|
|
186
186
|
|
|
187
187
|
Never paste raw command transcripts. For any failure, report only the step, exit code, and sanitized error. Redact the trial code and every `apex_at_`, `apex_rt_`, or `apex_ct_` value from all reports.
|
|
@@ -222,6 +222,7 @@ Supported shell commands:
|
|
|
222
222
|
- `/findings send <finding-ref> <linear|jira>`
|
|
223
223
|
- `/findings link-ticket <finding-ref> <linear|jira> <issue-key|url>`
|
|
224
224
|
- `/findings comment <finding-id|finding-identifier> <comment>`
|
|
225
|
+
- `/findings status <finding-ref> <open|fixed|acknowledged|in_review|pending_merge|false_positive|wont_fix|skipped> [note]`
|
|
225
226
|
- `/findings feedback <finding-id|finding-identifier> valid [comment]`
|
|
226
227
|
- `/findings feedback <finding-id|finding-identifier> invalid <false-positive|by-design|not-relevant> [comment]`
|
|
227
228
|
- `/findings fix-review <finding-id|finding-identifier>`
|
|
@@ -260,8 +261,9 @@ Supported shell commands:
|
|
|
260
261
|
- `apex findings send <finding-ref> <linear|jira> [--scan <scan-id>] [--json]`
|
|
261
262
|
- `apex findings link-ticket <finding-ref> <linear|jira> <issue-key|url> [--scan <scan-id>] [--json]`
|
|
262
263
|
- `apex findings comment <finding-id|finding-identifier> --content <markdown> [--parent-comment <comment-id>] [--scan <scan-id>]`
|
|
263
|
-
- `apex findings
|
|
264
|
-
- `apex findings
|
|
264
|
+
- `apex findings status <finding-ref> <open|fixed|acknowledged|in_review|pending_merge|false_positive|wont_fix|skipped> [--note <rationale>] [--expected-updated-at <timestamp>] [--scan <scan-id>]`
|
|
265
|
+
- `apex findings feedback <finding-id|finding-identifier> <valid|invalid> [comment] [--comment <markdown>] [--scan <scan-id>] [--suggested-severity extreme|critical|high|medium|low|informational] [--dismissal-reason false-positive|by-design|not-relevant] [--label acknowledged|fixed] [--fix-pr-url <fix-pr-url>]`
|
|
266
|
+
- `apex findings fix-review <finding-id|finding-identifier> [--scan <scan-id>] [--review-target pull-request|repository-current] [--fix-pr-url <fix-pr-url>]`
|
|
265
267
|
- `apex export findings [--scan <scan-id>] [--format markdown|json|gitlab-sast] [--output <path>]`
|
|
266
268
|
- `apex workspaces`
|
|
267
269
|
- `apex workspace`
|
|
@@ -310,7 +312,7 @@ The CLI automatically uses your company when only one is available. If you belon
|
|
|
310
312
|
|
|
311
313
|
The secret is returned only by `create` and cannot be retrieved later. Store it directly in a secret manager and do not put it in source control, prompts, chat messages, or logs. `--json` also includes the shown-once secret so it can be captured programmatically; handle that output as a credential.
|
|
312
314
|
|
|
313
|
-
Service keys are organization-level credentials. Grant only the scopes the integration needs: `scans:create` reads credit availability and triggers scans, `scans:read` polls status, and `findings:read` retrieves findings. Omit `--scope` to grant those three default scopes.
|
|
315
|
+
Service keys are organization-level credentials. Grant only the scopes the integration needs: `scans:create` reads credit availability and triggers scans, `scans:read` polls status, and `findings:read` retrieves findings. Omit `--scope` to grant those three default scopes. Finding status changes and ticket management additionally require explicitly requesting `findings:write`. For a P1-only adapter that reads manually started scans, create a read-only key with `--scope scans:read,findings:read`.
|
|
314
316
|
|
|
315
317
|
A key with `findings:read` can retrieve unpublished draft findings for scans in its organization so an external hub can perform triage. Give the key only to systems that are allowed to handle those drafts.
|
|
316
318
|
|
|
@@ -356,7 +358,7 @@ Claude Code can expand the same environment variable from `.mcp.json`:
|
|
|
356
358
|
}
|
|
357
359
|
```
|
|
358
360
|
|
|
359
|
-
The hosted server exposes `apex-credits`, `apex-workspaces`, `apex-scan`, `apex-black-start`, `apex-status`, `apex-cancel-scan`, `apex-findings`, `apex-workspace-findings`, `apex-finding-tickets`, `apex-finding-link-ticket`, and `apex-finding-
|
|
361
|
+
The hosted server exposes `apex-credits`, `apex-workspaces`, `apex-scan`, `apex-black-start`, `apex-status`, `apex-cancel-scan`, `apex-findings`, `apex-workspace-findings`, `apex-finding-tickets`, `apex-finding-link-ticket`, `apex-finding-send`, and `apex-finding-status`. Ticket creation and linking require an explicit `findings:write` scope and a connected Linear/Jira integration. Hosted operations are scoped to the authenticated organization. Repository scans use connected GitHub repositories; Apex Black scans use explicitly authorized HTTPS origins. Use local stdio MCP for device auth, local filesystem/archive access, provider connection, setup, comments, feedback, fix reviews, and trial flows.
|
|
360
362
|
|
|
361
363
|
Apex Black is hosted-only: use `apex-black-start` when the connected deployment advertises it and the organization has Apex Black beta access. Check `apex-credits` first. Supply 1–16 exact HTTPS origins, a stable `idempotencyKey`, and `authorizationAccepted: true` only when the user has authorized every target and accepted the 200 Standard credit cost; honor authorization already provided in the session. Use the returned Bedrock `scanId` with `apex-status` and `apex-findings`. An identical retry replays a stored start failure, including `APEX_BLACK_START_FAILED`; retain its durable IDs and contact support before trying another request. Local CLI and stdio MCP do not expose this web-scan tool or a Black scan mode.
|
|
362
364
|
|
|
@@ -466,7 +468,7 @@ Local MCP exposes `apex-finding-link-ticket` with `findingRef`, `provider`, `iss
|
|
|
466
468
|
|
|
467
469
|
## Local Source Scans
|
|
468
470
|
|
|
469
|
-
`apex scan [target...]` accepts files, directories, and HTTPS GitHub/GitLab repository URLs. With no targets, it scans the current working directory:
|
|
471
|
+
`apex scan [target...]` accepts files, directories, and HTTPS GitHub/GitLab or company-enabled Forgejo repository URLs. With no targets, it scans the current working directory:
|
|
470
472
|
|
|
471
473
|
```bash
|
|
472
474
|
apex scan . --mode standard
|
|
@@ -479,7 +481,7 @@ A file target uploads only that file, never its siblings. Local snapshots includ
|
|
|
479
481
|
|
|
480
482
|
Source planning:
|
|
481
483
|
|
|
482
|
-
- clean GitHub or
|
|
484
|
+
- clean GitHub, GitLab or company-enabled Forgejo checkouts can stay on the remote-materialization path
|
|
483
485
|
- dirty git worktrees fall back to a local snapshot upload by default
|
|
484
486
|
- plain directories that are not git repositories are scanned through a local snapshot upload
|
|
485
487
|
|
|
@@ -488,20 +490,20 @@ Useful flags:
|
|
|
488
490
|
- `--repo <path-or-url>` as a repeatable alternative to positional targets
|
|
489
491
|
- `--source-mode auto|remote|local` to control remote-first fallback behavior
|
|
490
492
|
- `--mode standard|audit|lite|pr` to choose the scan mode
|
|
491
|
-
- `--pr <number>` to select one or more GitHub pull requests for `--mode pr`
|
|
493
|
+
- `--pr <number>` to select one or more GitHub or enabled Forgejo pull requests for `--mode pr`
|
|
492
494
|
- `--pr <number:path,path>` or `--pr-path <path>` to limit a PR scan to changed paths
|
|
493
495
|
|
|
494
496
|
Unauthenticated `--json`, `--non-interactive`, and non-TTY commands fail immediately with `apex login` guidance. Run login explicitly before automation. Temporary refresh/network failures preserve credentials; retry after the service recovers. Provider connection commands return URLs without opening browsers in automation.
|
|
495
497
|
|
|
496
498
|
`auto` is the default. `remote` requires Apex to materialize from a remote repository. `local` forces a local snapshot upload even when a clean remote path is available.
|
|
497
499
|
|
|
498
|
-
Audit scans use `audit` as the scan mode and still require provider-backed GitHub or
|
|
500
|
+
Audit scans use `audit` as the scan mode and still require provider-backed GitHub, GitLab or company-enabled Forgejo repositories that Apex can materialize remotely without a local snapshot fallback. `ultra` remains accepted as a backwards-compatible alias.
|
|
499
501
|
|
|
500
502
|
Lite scans use `lite` as the scan mode and require the Lite Scan beta feature plus provider-backed GitHub or GitLab repositories that Apex can materialize remotely. Local snapshot uploads are not supported for Lite scans.
|
|
501
503
|
|
|
502
504
|
If a backend returns `AUDIT_SETUP_REQUIRED`, the error includes a setup command for the selected workspace. This explicit beta workflow uses the workspace's saved repositories and context; it does not preserve positional file or directory selections. Do not automatically substitute it for a local target scan. An explicit Standard source scan requires current CLI and backend versions. Eligible accounts receive setup commands in CLI help; draft operations use the ID returned by preparation. Lite and PR scans do not use `--setup`.
|
|
503
505
|
|
|
504
|
-
PR scans require exactly one provider-backed GitHub repository. If the current directory resolves to multiple sources, pass `--repo <path>` to select the one that contains the pull request.
|
|
506
|
+
PR scans require exactly one provider-backed GitHub or company-enabled, connected Forgejo repository. If the current directory resolves to multiple sources, pass `--repo <path>` to select the one that contains the pull request.
|
|
505
507
|
|
|
506
508
|
CLI and MCP scan results use the public Bedrock `scanId`. Kernel execution IDs are internal implementation details and are neither returned nor accepted as scan aliases.
|
|
507
509
|
|
|
@@ -617,6 +619,7 @@ The local stdio MCP server exposes Apex-specific tools for:
|
|
|
617
619
|
- cross-scan workspace findings through `apex-workspace-findings`, including direct workspace selection, first-class open state, web UI lifecycle fields, and server-side filters
|
|
618
620
|
- Lite scan start by calling `apex-scan` with `mode: "lite"`
|
|
619
621
|
- PR scan start by calling `apex-scan` with `mode: "pr"` and `pullRequests`
|
|
622
|
+
- close/reopen findings with `apex-finding-status`, preserving scanner validity and feedback
|
|
620
623
|
- finding comments and valid/invalid feedback with `apex-finding-comment` and `apex-finding-feedback`
|
|
621
624
|
- Create Linear/Jira tickets with `apex-finding-send` using the saved frontend destination
|
|
622
625
|
- Link existing Linear/Jira tickets with `apex-finding-link-ticket` and inspect links with `apex-finding-tickets`
|
|
@@ -684,7 +687,7 @@ Telemetry event posts do not include bearer tokens. The telemetry endpoint is `P
|
|
|
684
687
|
{
|
|
685
688
|
"schemaVersion": 1,
|
|
686
689
|
"event": "apex.invocation.completed",
|
|
687
|
-
"cliVersion": "0.1.
|
|
690
|
+
"cliVersion": "0.1.39",
|
|
688
691
|
"invocation": {
|
|
689
692
|
"surface": "mcp",
|
|
690
693
|
"command": "apex-scan",
|
|
@@ -715,7 +718,7 @@ The npm package also includes marketplace-ready plugin artifacts:
|
|
|
715
718
|
- `.claude-plugin/plugin.json` and `.mcp.claude.json` for Claude Code plugin installs
|
|
716
719
|
- `.claude-plugin/marketplace.json` for a Claude marketplace entry backed by the public npm package
|
|
717
720
|
|
|
718
|
-
These plugin installs launch the pinned npm package with `npx -y -p @cantinasecurity/apex-cli@0.1.
|
|
721
|
+
These plugin installs launch the pinned npm package with `npx -y -p @cantinasecurity/apex-cli@0.1.39 apex-mcp`, so users do not need to install `apex` globally before enabling the plugin.
|
|
719
722
|
|
|
720
723
|
The repository also includes `.agents/plugins/marketplace.json` for local Codex marketplace testing from a checkout.
|
|
721
724
|
|
|
@@ -785,3 +788,41 @@ Keep `policy.version` at `3.1`. Supply a name, instructions, and all eight metri
|
|
|
785
788
|
allowed value and nonempty guidance for each allowed value. Omit disallowed
|
|
786
789
|
values. If Audit Setup or another workspace update is active, wait for it to
|
|
787
790
|
finish before saving again.
|
|
791
|
+
|
|
792
|
+
### Close and reopen findings
|
|
793
|
+
|
|
794
|
+
Use `apex-finding-status` to manage remediation directly from either MCP transport.
|
|
795
|
+
Local MCP accepts `findingRef` and optional `cwd`/`scanId` using device login.
|
|
796
|
+
Hosted MCP accepts `findingId` (UUID) and requires explicitly granted `findings:write`.
|
|
797
|
+
Pass `status: "fixed"` to close an issue or `status: "open"` to reopen it.
|
|
798
|
+
|
|
799
|
+
```sh
|
|
800
|
+
apex findings status KERN2-25 fixed --note "Resolved in PR #123" --json
|
|
801
|
+
apex findings status KERN2-25 open --note "Regression reproduced" --json
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
Other statuses are `acknowledged`, `in_review`, `pending_merge`, `false_positive`,
|
|
805
|
+
`wont_fix`, and `skipped`. Reopening stores `accepted`, retaining the fact that the
|
|
806
|
+
finding was reviewed. Status changes preserve scanner validity, severity, and
|
|
807
|
+
valid/invalid feedback; invalid or duplicate findings must have their classification
|
|
808
|
+
reviewed before they can enter the open queue.
|
|
809
|
+
|
|
810
|
+
REST integrations call `PATCH /api/apex/v1/findings/{findingId}/status` with
|
|
811
|
+
`{"status":"fixed","note":"Resolved in PR #123"}` and a bearer service key with
|
|
812
|
+
`findings:write`. Each change is audited. The response includes `findingId`,
|
|
813
|
+
`status`, `reviewState`, `fromReviewState`, `updatedAt`, and `changed`.
|
|
814
|
+
Workspace finding lists expose `remediationStatus`, keeping `open` and `acknowledged`
|
|
815
|
+
distinct from the scanner `status` and the persisted `reviewState`.
|
|
816
|
+
Pass the current finding `updatedAt` as `expectedUpdatedAt` (CLI:
|
|
817
|
+
`--expected-updated-at`) to reject stale edits with `FINDING_STATUS_CONFLICT` (409).
|
|
818
|
+
Reload and review the latest state before retrying a conflict.
|
|
819
|
+
|
|
820
|
+
Linear sync currently runs from Linear to Apex: started tickets become acknowledged
|
|
821
|
+
and completed tickets become fixed. Apex status changes do not update linked Linear
|
|
822
|
+
or Jira tickets. Use the direct status operation to manage issues from MCP or REST.
|
|
823
|
+
|
|
824
|
+
## Forgejo
|
|
825
|
+
|
|
826
|
+
Forgejo connections, remote repository scans, PR scans and Fix Review require the Forgejo company feature enabled by an administrator in Bedrock Settings. Run `apex connect forgejo` (or `apex-connect-provider` with `provider: "forgejo"`) to obtain the browser connection URL. A dedicated bot, HTTPS instance URL and repository-scoped read token are required; PR statuses, comments and webhook installation also require a write token.
|
|
827
|
+
|
|
828
|
+
After connecting, run `apex doctor --repo . --source-mode remote`, then `apex scan --mode standard --repo . --source-mode remote` or `apex scan --mode pr --pr 42 --repo .`. Same-repository Forgejo PR URLs use `/owner/repository/pulls/42`. Pass a Forgejo Fix PR URL through finding feedback and start Fix Review using the existing finding commands or MCP tools. Disabling the company flag stops native Forgejo access. Existing local Standard snapshots remain provider-independent.
|
package/dist/apex.js
CHANGED
|
@@ -3,7 +3,7 @@ import { commandStartThreatModel, commandThreatModel, hasAuditSetupBeta, threatM
|
|
|
3
3
|
import { getMe } from "./auth.js";
|
|
4
4
|
import { ApexApiClient, formatApiError } from "./api-client.js";
|
|
5
5
|
import { isAuditSetupInvocation, parseArgs, validateCommandFlags } from "./args.js";
|
|
6
|
-
import { commandCancelScan, commandClaim, commandConnect, commandCredits, commandDoctor, commandExportFindings, commandFindingComment, commandFindingTickets, commandFindingLinkTicket, commandFindingSend, commandFindingFeedback, commandFindingFixReview, commandFindings, commandWorkspaceFindings, commandLogin, commandLogout, commandRedeem, commandScan, commandScans, commandServiceKeyCreate, commandServiceKeyList, commandServiceKeyRevoke, commandSetup, commandStatus, commandTelemetry, commandUpdate, commandWorkspace, commandWorkspaceUse, commandWorkspaces, getRedeemExitCode, } from "./commands.js";
|
|
6
|
+
import { commandCancelScan, commandClaim, commandConnect, commandCredits, commandDoctor, commandExportFindings, commandFindingComment, commandFindingStatus, commandFindingTickets, commandFindingLinkTicket, commandFindingSend, commandFindingFeedback, commandFindingFixReview, commandFindings, commandWorkspaceFindings, commandLogin, commandLogout, commandRedeem, commandScan, commandScans, commandServiceKeyCreate, commandServiceKeyList, commandServiceKeyRevoke, commandSetup, commandStatus, commandTelemetry, commandUpdate, commandWorkspace, commandWorkspaceUse, commandWorkspaces, getRedeemExitCode, } from "./commands.js";
|
|
7
7
|
import { cliHelpText } from "./help.js";
|
|
8
8
|
import { runMcpServer } from "./mcp.js";
|
|
9
9
|
import { runInteractiveShell } from "./shell.js";
|
|
@@ -109,6 +109,12 @@ async function dispatch(parsed) {
|
|
|
109
109
|
await commandWorkspace(cwd, parsed.flags);
|
|
110
110
|
return;
|
|
111
111
|
case "findings":
|
|
112
|
+
if (parsed.subcommand === "status") {
|
|
113
|
+
if (parsed.args.length !== 2)
|
|
114
|
+
throw new Error("Usage: apex findings status <finding-ref> <open|fixed|acknowledged|in_review|pending_merge|false_positive|wont_fix|skipped> [--note <rationale>]");
|
|
115
|
+
await commandFindingStatus(client, cwd, parsed.flags, parsed.args[0], parsed.args[1], typeof parsed.flags.note === "string" ? parsed.flags.note : undefined, typeof parsed.flags["expected-updated-at"] === "string" ? parsed.flags["expected-updated-at"] : undefined);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
112
118
|
if (parsed.subcommand === "send") {
|
|
113
119
|
if (parsed.args.length !== 2)
|
|
114
120
|
throw new Error("Usage: apex findings send <finding-ref> <linear|jira>");
|
|
@@ -176,8 +182,8 @@ async function dispatch(parsed) {
|
|
|
176
182
|
await commandStatus(client, cwd, parsed.flags);
|
|
177
183
|
return;
|
|
178
184
|
case "connect":
|
|
179
|
-
if (parsed.subcommand !== "github" && parsed.subcommand !== "gitlab") {
|
|
180
|
-
throw new Error("Usage: apex connect <github|gitlab>");
|
|
185
|
+
if (parsed.subcommand !== "github" && parsed.subcommand !== "gitlab" && parsed.subcommand !== "forgejo") {
|
|
186
|
+
throw new Error("Usage: apex connect <github|gitlab|forgejo>");
|
|
181
187
|
}
|
|
182
188
|
await commandConnect(client, cwd, parsed.flags, parsed.subcommand);
|
|
183
189
|
return;
|
package/dist/args.js
CHANGED
|
@@ -14,6 +14,7 @@ export const KNOWN_CLI_FLAG_NAMES = [
|
|
|
14
14
|
"content",
|
|
15
15
|
"dismissal-reason",
|
|
16
16
|
"expires-at",
|
|
17
|
+
"expected-updated-at",
|
|
17
18
|
"fix-pr-url",
|
|
18
19
|
"force",
|
|
19
20
|
"format",
|
|
@@ -25,6 +26,7 @@ export const KNOWN_CLI_FLAG_NAMES = [
|
|
|
25
26
|
"limit",
|
|
26
27
|
"mode",
|
|
27
28
|
"name",
|
|
29
|
+
"note",
|
|
28
30
|
"no-open",
|
|
29
31
|
"non-interactive",
|
|
30
32
|
"output",
|
package/dist/commands.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { parseForgejoPullRequestUrl } from "./forgejo-url.js";
|
|
1
2
|
import { canPromptInteractively, getFlagList, getFlagString, isJsonMode, } from "./args.js";
|
|
2
3
|
import { getMe, login, logout } from "./auth.js";
|
|
3
4
|
import { ApiError, formatApiError, getApiErrorCode, } from "./api-client.js";
|
|
4
5
|
import { openInBrowser } from "./browser.js";
|
|
5
6
|
import { getClaimProofState, isClaimProofAvailable, parseClaimDeadlineMs, } from "./claim-proof.js";
|
|
6
7
|
import { loadConfig, updateConfig } from "./config.js";
|
|
7
|
-
import { createFindingComment, fetchFindingTickets, linkFindingTicket, sendFindingToIssueTracker, fetchScanExport, fetchScanFindings, fetchWorkspaceFindings, isFindingUuid, normalizeFindingRef, resolveFindingSelection, resolveScanSelection, startFindingFixReviewScan, submitFindingFeedback, } from "./findings.js";
|
|
8
|
+
import { createFindingComment, fetchFindingTickets, linkFindingTicket, sendFindingToIssueTracker, fetchScanExport, fetchScanFindings, fetchWorkspaceFindings, isFindingUuid, normalizeFindingRef, resolveFindingSelection, resolveScanSelection, startFindingFixReviewScan, submitFindingFeedback, setFindingStatus, } from "./findings.js";
|
|
9
|
+
import { FINDING_LIFECYCLE_STATUSES } from "./types.js";
|
|
8
10
|
import { prepareLocalSourceArchives, prepareExplicitScanSources, supportsLegacyRemoteFlow, } from "./local-source-scan.js";
|
|
9
11
|
import { logLine, printJson, withLoadingIndicator } from "./output.js";
|
|
10
12
|
import { claimOnboardingSession, isProofOnlyOnboardingCredentials, redeemOnboardingCode, revokeGuestOnboardingSession, } from "./onboarding.js";
|
|
@@ -370,6 +372,10 @@ function normalizeRepositoryUrl(value) {
|
|
|
370
372
|
return value.replace(/\.git$/i, "").replace(/\/+$/, "").toLowerCase();
|
|
371
373
|
}
|
|
372
374
|
function parseGithubPullRequestUrl(value) {
|
|
375
|
+
const forgejoWithPaths = value.match(/^(https?:\/\/[^\s]+\/pulls\/\d+):(.+)$/);
|
|
376
|
+
const forgejo = parseForgejoPullRequestUrl(forgejoWithPaths?.[1] ?? value);
|
|
377
|
+
if (forgejo)
|
|
378
|
+
return { number: forgejo.prNumber, repoUrl: forgejo.baseUrl + "/" + forgejo.repoName + ".git", pathFilterValue: forgejoWithPaths?.[2] ?? null };
|
|
373
379
|
const withPathFilter = value.match(/^(https?:\/\/github\.com\/[^/\s]+\/[^/\s]+\/pull\/\d+):(.+)$/i);
|
|
374
380
|
const urlValue = withPathFilter?.[1] ?? value;
|
|
375
381
|
const match = urlValue.match(/^https?:\/\/github\.com\/([^/\s]+)\/([^/\s]+)\/pull\/(\d+)(?:[/?#].*)?$/i);
|
|
@@ -456,10 +462,12 @@ function resolvePrScanSource(repositories) {
|
|
|
456
462
|
throw new Error("PR scans require exactly one GitHub repository. Re-run with --repo to select a single source root.");
|
|
457
463
|
}
|
|
458
464
|
const source = repositories[0];
|
|
459
|
-
if (source.provider !== "github") {
|
|
460
|
-
throw new Error("PR scans require a provider-backed GitHub repository.");
|
|
465
|
+
if (source.provider !== "github" && source.provider !== "forgejo") {
|
|
466
|
+
throw new Error("PR scans require a provider-backed GitHub repository or an enabled Forgejo repository.");
|
|
461
467
|
}
|
|
462
|
-
if (
|
|
468
|
+
if (source.provider === "forgejo" && (!source.connectionId || !source.projectId || !source.baseUrl))
|
|
469
|
+
throw new Error("PR scans require a connected Forgejo repository.");
|
|
470
|
+
if (source.provider === "github" && typeof source.installationId !== "number") {
|
|
463
471
|
throw new Error("PR scans require GitHub App installation access for the selected repository.");
|
|
464
472
|
}
|
|
465
473
|
return source;
|
|
@@ -480,9 +488,10 @@ function formatMissingProviderConnections(missing) {
|
|
|
480
488
|
function buildPrScanRepositoriesPayload(params) {
|
|
481
489
|
return [
|
|
482
490
|
{
|
|
483
|
-
provider:
|
|
491
|
+
provider: params.source.provider,
|
|
484
492
|
repoUrl: params.source.repoUrl,
|
|
485
493
|
installationId: params.source.installationId,
|
|
494
|
+
...(params.source.provider === "forgejo" ? { connectionId: params.source.connectionId, projectId: params.source.projectId, baseUrl: params.source.baseUrl } : {}),
|
|
486
495
|
sourceType: "pr",
|
|
487
496
|
pullRequests: params.pullRequests.map((pullRequest) => ({
|
|
488
497
|
number: pullRequest.number,
|
|
@@ -507,7 +516,7 @@ function buildPrScanPlannedSource(source) {
|
|
|
507
516
|
displayName: getPrScanSourceDisplayName(source),
|
|
508
517
|
relativePath: source.path || ".",
|
|
509
518
|
sourceKind: "remote_repo",
|
|
510
|
-
provider:
|
|
519
|
+
provider: source.provider,
|
|
511
520
|
repoUrl: source.repoUrl,
|
|
512
521
|
ref: {
|
|
513
522
|
type: "default",
|
|
@@ -517,7 +526,7 @@ function buildPrScanPlannedSource(source) {
|
|
|
517
526
|
connectionId: source.connectionId ?? null,
|
|
518
527
|
projectId: source.projectId ?? null,
|
|
519
528
|
baseUrl: source.baseUrl ?? null,
|
|
520
|
-
materializationAuth: "installation",
|
|
529
|
+
materializationAuth: source.provider === "forgejo" ? "connection" : "installation",
|
|
521
530
|
};
|
|
522
531
|
}
|
|
523
532
|
function getPrScanSelectedSources(sources, source) {
|
|
@@ -1069,9 +1078,7 @@ export async function commandConnect(client, cwd, flags, provider) {
|
|
|
1069
1078
|
const me = await ensureAuthenticated(client, flags);
|
|
1070
1079
|
const binding = await loadWorkspaceBinding(cwd);
|
|
1071
1080
|
const company = await chooseCompany(me, flags, binding);
|
|
1072
|
-
const route = provider
|
|
1073
|
-
? `/api/cli/v1/providers/github/connect?companyId=${encodeURIComponent(company.id)}`
|
|
1074
|
-
: `/api/cli/v1/providers/gitlab/connect?companyId=${encodeURIComponent(company.id)}`;
|
|
1081
|
+
const route = `/api/cli/v1/providers/${provider}/connect?companyId=${encodeURIComponent(company.id)}`;
|
|
1075
1082
|
const response = await withLoadingIndicator(`Preparing ${provider} connection link...`, flags, () => client.request(route));
|
|
1076
1083
|
if (!isJsonMode(flags)) {
|
|
1077
1084
|
logLine(`Connection URL for ${provider}: ${response.url}`, flags);
|
|
@@ -1896,7 +1903,7 @@ export async function openCurrentApexView(client, cwd, flags) {
|
|
|
1896
1903
|
}
|
|
1897
1904
|
return url;
|
|
1898
1905
|
}
|
|
1899
|
-
async function
|
|
1906
|
+
async function resolveCommandFinding(client, cwd, flags, requestedFindingRef) {
|
|
1900
1907
|
const findingRef = normalizeFindingRefInput(requestedFindingRef);
|
|
1901
1908
|
const currentBinding = await loadWorkspaceBinding(cwd);
|
|
1902
1909
|
const binding = isFindingUuid(findingRef)
|
|
@@ -1907,7 +1914,7 @@ async function resolveTicketFinding(client, cwd, flags, requestedFindingRef) {
|
|
|
1907
1914
|
requestedScanId: getFlagString(flags, "scan") });
|
|
1908
1915
|
}
|
|
1909
1916
|
export async function commandFindingTickets(client, cwd, flags, findingRef) {
|
|
1910
|
-
const selection = await
|
|
1917
|
+
const selection = await resolveCommandFinding(client, cwd, flags, findingRef);
|
|
1911
1918
|
const result = await fetchFindingTickets(client, selection.findingId);
|
|
1912
1919
|
const payload = { findingId: selection.findingId, findingRef: selection.findingRef, ...result };
|
|
1913
1920
|
if (isJsonMode(flags))
|
|
@@ -1923,7 +1930,7 @@ export async function commandFindingLinkTicket(client, cwd, flags, findingRef, p
|
|
|
1923
1930
|
if ((provider !== "linear" && provider !== "jira") || !issueRef.trim() || issueRef.trim().length > 2048) {
|
|
1924
1931
|
throw new Error("Usage: apex findings link-ticket <finding-ref> <linear|jira> <issue-key|url>");
|
|
1925
1932
|
}
|
|
1926
|
-
const selection = await
|
|
1933
|
+
const selection = await resolveCommandFinding(client, cwd, flags, findingRef);
|
|
1927
1934
|
const result = await linkFindingTicket(client, selection.findingId, { provider, issueRef: issueRef.trim() });
|
|
1928
1935
|
const payload = { findingId: selection.findingId, findingRef: selection.findingRef, ...result };
|
|
1929
1936
|
if (isJsonMode(flags))
|
|
@@ -1936,7 +1943,7 @@ export async function commandFindingSend(client, cwd, flags, findingRef, provide
|
|
|
1936
1943
|
if (provider !== "linear" && provider !== "jira") {
|
|
1937
1944
|
throw new Error("Usage: apex findings send <finding-ref> <linear|jira>");
|
|
1938
1945
|
}
|
|
1939
|
-
const selection = await
|
|
1946
|
+
const selection = await resolveCommandFinding(client, cwd, flags, findingRef);
|
|
1940
1947
|
const result = await sendFindingToIssueTracker(client, selection.findingId, provider);
|
|
1941
1948
|
const payload = { findingId: selection.findingId, findingRef: selection.findingRef, ...result };
|
|
1942
1949
|
if (isJsonMode(flags))
|
|
@@ -1945,3 +1952,29 @@ export async function commandFindingSend(client, cwd, flags, findingRef, provide
|
|
|
1945
1952
|
logLine(`${result.created ? "Created" : "Already sent"}: ${result.export.issueKey ?? result.export.issueId} ${result.export.issueUrl}`, flags);
|
|
1946
1953
|
return payload;
|
|
1947
1954
|
}
|
|
1955
|
+
export async function commandFindingStatus(client, cwd, flags, findingRef, status, note, expectedUpdatedAt) {
|
|
1956
|
+
if (!FINDING_LIFECYCLE_STATUSES.includes(status)) {
|
|
1957
|
+
throw new Error(`Usage: apex findings status <finding-ref> <${FINDING_LIFECYCLE_STATUSES.join("|")}> [--note <rationale>]`);
|
|
1958
|
+
}
|
|
1959
|
+
const trimmedNote = note?.trim();
|
|
1960
|
+
if (trimmedNote && trimmedNote.length > 1000)
|
|
1961
|
+
throw new Error("Status notes must be 1000 characters or less.");
|
|
1962
|
+
if (expectedUpdatedAt && (!/^\d{4}-\d{2}-\d{2}T/.test(expectedUpdatedAt) || !Number.isFinite(Date.parse(expectedUpdatedAt)))) {
|
|
1963
|
+
throw new Error("--expected-updated-at must be an ISO timestamp from the finding's updatedAt.");
|
|
1964
|
+
}
|
|
1965
|
+
const selection = await resolveCommandFinding(client, cwd, flags, findingRef);
|
|
1966
|
+
const result = await setFindingStatus(client, selection.findingId, {
|
|
1967
|
+
status: status,
|
|
1968
|
+
...(trimmedNote ? { note: trimmedNote } : {}),
|
|
1969
|
+
...(expectedUpdatedAt ? { expectedUpdatedAt } : {}),
|
|
1970
|
+
});
|
|
1971
|
+
const payload = { ...result, findingRef: selection.findingRef };
|
|
1972
|
+
if (isJsonMode(flags))
|
|
1973
|
+
printJson(payload);
|
|
1974
|
+
else {
|
|
1975
|
+
logLine(`Finding: ${selection.findingRef}`, flags);
|
|
1976
|
+
logLine(`Status: ${result.status} (${result.reviewState})${result.changed ? "" : " — already set"}`, flags);
|
|
1977
|
+
logLine("Scanner validity and review feedback are unchanged.", flags);
|
|
1978
|
+
}
|
|
1979
|
+
return payload;
|
|
1980
|
+
}
|
package/dist/findings.js
CHANGED
|
@@ -5,7 +5,7 @@ const WORKSPACE_FINDING_FILTER_PATTERN = /^!?(severity|scan|repo|review|impact|l
|
|
|
5
5
|
const MAX_WORKSPACE_FINDING_FILTERS = 50;
|
|
6
6
|
const MAX_WORKSPACE_FINDING_FILTER_LENGTH = 128;
|
|
7
7
|
const CANTINA_AUTH_TOKEN_ENV = "CANTINA_AUTH_TOKEN";
|
|
8
|
-
const FINDING_WRITE_AUTH_ERROR = "Finding comments, feedback, ticket links, ticket creation, and fix review scans require Apex authentication. Run `apex login` or use MCP `apex-auth-start` followed by `apex-auth-wait`, then retry.";
|
|
8
|
+
const FINDING_WRITE_AUTH_ERROR = "Finding comments, feedback, status changes, ticket links, ticket creation, and fix review scans require Apex authentication. Run `apex login` or use MCP `apex-auth-start` followed by `apex-auth-wait`, then retry.";
|
|
9
9
|
function parsePositiveInt(value) {
|
|
10
10
|
if (!value)
|
|
11
11
|
return null;
|
|
@@ -321,6 +321,11 @@ export async function startFindingFixReviewScan(client, findingId, input = {}) {
|
|
|
321
321
|
export async function fetchFindingTickets(client, findingId) {
|
|
322
322
|
return requestFindingAppRoute(client, `/api/findings/${encodeURIComponent(findingId)}/tickets`);
|
|
323
323
|
}
|
|
324
|
+
export async function setFindingStatus(client, findingId, input) {
|
|
325
|
+
return requestFindingAppRoute(client, `/api/findings/${encodeURIComponent(findingId)}/status`, {
|
|
326
|
+
method: "PATCH", json: input,
|
|
327
|
+
});
|
|
328
|
+
}
|
|
324
329
|
export async function linkFindingTicket(client, findingId, input) {
|
|
325
330
|
return requestFindingAppRoute(client, `/api/findings/${encodeURIComponent(findingId)}/tickets`, {
|
|
326
331
|
method: "POST", json: input,
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export function normalizeForgejoBaseUrl(value) {
|
|
2
|
+
try {
|
|
3
|
+
const url = new URL(value.trim());
|
|
4
|
+
if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash)
|
|
5
|
+
return null;
|
|
6
|
+
if (!url.hostname || /^(localhost|127\.|0\.|\[::1\])/i.test(url.hostname))
|
|
7
|
+
return null;
|
|
8
|
+
return url.origin + url.pathname.replace(/\/+$/, "");
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function parseForgejoRepository(value, baseUrl) {
|
|
15
|
+
const base = normalizeForgejoBaseUrl(baseUrl);
|
|
16
|
+
if (!base)
|
|
17
|
+
return null;
|
|
18
|
+
try {
|
|
19
|
+
const url = new URL(value.includes("://") ? value : base + "/" + value.replace(/^\/+/, ""));
|
|
20
|
+
if (url.username || url.password || url.search || url.hash || !url.href.startsWith(base + "/"))
|
|
21
|
+
return null;
|
|
22
|
+
const segments = url.pathname.slice(new URL(base).pathname.replace(/\/+$/, "").length).replace(/^\/+/, "").replace(/\.git$/, "").split("/");
|
|
23
|
+
if (segments.length !== 2 || segments.some(part => !/^[a-z0-9_.-]+$/i.test(part) || part === "." || part === ".."))
|
|
24
|
+
return null;
|
|
25
|
+
const [owner, repo] = segments;
|
|
26
|
+
return { provider: "forgejo", baseUrl: base, owner, repo, repoName: owner + "/" + repo, normalizedUrl: base + "/" + owner + "/" + repo };
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function parseForgejoPullRequestUrl(value) {
|
|
33
|
+
try {
|
|
34
|
+
const url = new URL(value.trim());
|
|
35
|
+
const match = url.pathname.match(/^(.*)\/([^/]+)\/([^/]+)\/pulls\/(\d+)(?:\/.*)?$/);
|
|
36
|
+
if (!match || url.username || url.password)
|
|
37
|
+
return null;
|
|
38
|
+
const identity = parseForgejoRepository(match[2] + "/" + match[3], url.origin + match[1]);
|
|
39
|
+
const prNumber = Number(match[4]);
|
|
40
|
+
if (!identity || !Number.isSafeInteger(prNumber) || prNumber <= 0)
|
|
41
|
+
return null;
|
|
42
|
+
return { ...identity, prNumber, normalizedUrl: identity.normalizedUrl + "/pulls/" + prNumber };
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export function forgejoRepositoryPath(repoName) {
|
|
49
|
+
const parts = repoName.split("/");
|
|
50
|
+
if (parts.length !== 2 || parts.some(part => !/^[a-z0-9_.-]+$/i.test(part) || part === "." || part === ".."))
|
|
51
|
+
throw new Error("Invalid Forgejo repository path");
|
|
52
|
+
return "/repos/" + parts.map(encodeURIComponent).join("/");
|
|
53
|
+
}
|
package/dist/help.js
CHANGED
|
@@ -14,6 +14,8 @@ export const CLI_HELP_TEXT = `Usage:
|
|
|
14
14
|
Add a comment or note to a finding
|
|
15
15
|
apex findings feedback <finding-id|finding-identifier> <valid|invalid>
|
|
16
16
|
Leave feedback; invalid requires --dismissal-reason
|
|
17
|
+
apex findings status <finding-ref> <open|fixed|acknowledged|in_review|pending_merge|false_positive|wont_fix|skipped>
|
|
18
|
+
Close, reopen, or update client remediation status
|
|
17
19
|
apex findings fix-review <finding-id|finding-identifier>
|
|
18
20
|
Start a fix review scan for a Fix PR or current repository
|
|
19
21
|
apex export findings Export findings for the latest or selected scan
|
|
@@ -41,6 +43,7 @@ export const CLI_HELP_TEXT = `Usage:
|
|
|
41
43
|
apex update Update the local Apex CLI install
|
|
42
44
|
apex connect github Open the GitHub connection flow
|
|
43
45
|
apex connect gitlab Open the GitLab connection flow
|
|
46
|
+
apex connect forgejo Connect company-enabled Forgejo access
|
|
44
47
|
|
|
45
48
|
Flags:
|
|
46
49
|
--company <id-or-handle> Override the automatically selected Apex company
|
|
@@ -56,13 +59,15 @@ Flags:
|
|
|
56
59
|
--status valid|invalid Set the finding feedback status explicitly
|
|
57
60
|
--content <markdown> Supply comment content for apex findings comment
|
|
58
61
|
--comment <markdown> Supply rationale for apex findings feedback
|
|
62
|
+
--note <rationale> Audit note for a finding status change (max 1000 characters)
|
|
63
|
+
--expected-updated-at <timestamp> Reject a status change if the finding has changed since this updatedAt
|
|
59
64
|
--parent-comment <comment-id> Reply to an existing finding comment
|
|
60
65
|
--suggested-severity extreme|critical|high|medium|low|informational
|
|
61
66
|
Attach a suggested severity to valid feedback
|
|
62
67
|
--dismissal-reason false-positive|by-design|not-relevant
|
|
63
68
|
Explain why invalid feedback is being left
|
|
64
69
|
--label acknowledged|fixed Attach a valid-feedback label
|
|
65
|
-
--fix-pr-url <
|
|
70
|
+
--fix-pr-url <fix-pr-url> Attach one or more Fix PR URLs to feedback or fix-review start
|
|
66
71
|
--review-target pull-request|repository-current
|
|
67
72
|
Choose the fix review target when starting a fix review
|
|
68
73
|
--format markdown|json|gitlab-sast
|
|
@@ -70,7 +75,7 @@ Flags:
|
|
|
70
75
|
--output <path> Write exported findings to this file path
|
|
71
76
|
--limit <count> Limit the number of findings returned
|
|
72
77
|
--mode standard|audit|lite|pr Choose the scan mode
|
|
73
|
-
--pr <number[:path,path]> Select a GitHub
|
|
78
|
+
--pr <number[:path,path]> Select a GitHub or Forgejo PR for --mode pr
|
|
74
79
|
--pr-path <path> Limit a single-PR scan to a changed file path
|
|
75
80
|
--source-mode auto|remote|local Control remote-vs-local source materialization
|
|
76
81
|
--force Start an overlapping non-PR scan, or confirm destructive guest logout
|
|
@@ -86,7 +91,7 @@ Tips:
|
|
|
86
91
|
apex scan defaults to the current directory; positional targets or --repo select files, directories, or HTTPS GitHub/GitLab repositories.
|
|
87
92
|
A file target scans only that file. Use --mode standard for local changes and public repositories without a provider connection.
|
|
88
93
|
Headless and --json commands require an existing session; run apex login explicitly first.
|
|
89
|
-
PR scans require --mode pr and at least one --pr selection for a GitHub repository.
|
|
94
|
+
PR scans require --mode pr and at least one --pr selection for a connected GitHub or company-enabled Forgejo repository.
|
|
90
95
|
audit is the current scan mode for audit scans; ultra remains accepted as a legacy alias.
|
|
91
96
|
Lite scans use --mode lite and require beta access plus provider-backed GitHub or GitLab sources.
|
|
92
97
|
apex workspace use accepts a workspace name, prefix, or ID.
|
|
@@ -163,6 +168,8 @@ Commands:
|
|
|
163
168
|
Add a comment or note to a finding
|
|
164
169
|
/findings feedback <finding-ref> valid [comment]
|
|
165
170
|
Leave valid feedback on a finding
|
|
171
|
+
/findings status <finding-ref> <open|fixed|acknowledged|in_review|pending_merge|false_positive|wont_fix|skipped> [note]
|
|
172
|
+
Close, reopen, or update client remediation status
|
|
166
173
|
/findings feedback <finding-ref> invalid <reason> [comment]
|
|
167
174
|
Leave invalid feedback; reason is false-positive, by-design, or not-relevant
|
|
168
175
|
/findings fix-review <finding-ref>
|
|
@@ -184,6 +191,7 @@ Commands:
|
|
|
184
191
|
/company [id|handle] Show or switch the active company
|
|
185
192
|
/connect github Connect GitHub access in the browser
|
|
186
193
|
/connect gitlab Connect GitLab access in the browser
|
|
194
|
+
/connect forgejo Connect company-enabled Forgejo access
|
|
187
195
|
/open Open the latest scan or Apex home in the browser
|
|
188
196
|
/clear Clear the terminal
|
|
189
197
|
/help Show this help
|
|
@@ -200,7 +208,7 @@ Scripted-only commands (run outside the interactive shell):
|
|
|
200
208
|
Revoke a service key
|
|
201
209
|
|
|
202
210
|
Tips:
|
|
203
|
-
PR scans require a GitHub PR number and provider-backed
|
|
211
|
+
PR scans require a GitHub or enabled Forgejo PR number and provider-backed access.
|
|
204
212
|
audit is the current scan mode for audit scans; ultra remains accepted as a legacy alias.
|
|
205
213
|
Lite scans require beta access plus provider-backed GitHub or GitLab sources.
|
|
206
214
|
/workspace use accepts a workspace name, prefix, or ID.
|
package/dist/mcp.js
CHANGED
|
@@ -6,11 +6,12 @@ import { z } from "zod";
|
|
|
6
6
|
import { getMe, logout, readStableDeviceLoginState, startDeviceLogin, waitForDeviceLoginApproval, } from "./auth.js";
|
|
7
7
|
import { ApexApiClient, formatApiError } from "./api-client.js";
|
|
8
8
|
import { getClaimProofState, isClaimProofAvailable, } from "./claim-proof.js";
|
|
9
|
-
import { commandCancelScan, commandConnect, commandCredits, commandDoctor, commandExportFindings, commandFindingComment, commandFindingTickets, commandFindingLinkTicket, commandFindingSend, commandFindingFeedback, commandFindingFixReview, commandFindings, commandWorkspaceFindings, commandScan, commandScans, commandStatus, commandWorkspace, commandWorkspaceUse, commandWorkspaces, } from "./commands.js";
|
|
9
|
+
import { commandCancelScan, commandConnect, commandCredits, commandDoctor, commandExportFindings, commandFindingComment, commandFindingStatus, commandFindingTickets, commandFindingLinkTicket, commandFindingSend, commandFindingFeedback, commandFindingFixReview, commandFindings, commandWorkspaceFindings, commandScan, commandScans, commandStatus, commandWorkspace, commandWorkspaceUse, commandWorkspaces, } from "./commands.js";
|
|
10
10
|
import { cliHelpText } from "./help.js";
|
|
11
11
|
import { getLocalTrialStatus, isProofOnlyOnboardingCredentials, revokeGuestOnboardingSession, } from "./onboarding.js";
|
|
12
12
|
import { createMcpServerTelemetryInvocation, createMcpTelemetryInvocation, emitInvocationCompleted, emitInvocationStarted, emitMcpServerStarted, withTelemetryContext, } from "./telemetry.js";
|
|
13
13
|
import { APEX_CLI_VERSION } from "./version.js";
|
|
14
|
+
import { FINDING_LIFECYCLE_STATUSES } from "./types.js";
|
|
14
15
|
const MCP_WORKFLOW_GUIDE = `Use Apex through these tools instead of shelling out to the CLI.
|
|
15
16
|
|
|
16
17
|
Suggested workflow:
|
|
@@ -23,7 +24,7 @@ Suggested workflow:
|
|
|
23
24
|
7. Start and monitor scans with apex-scan, apex-status, apex-scans, apex-findings, and apex-export-findings. For cross-scan review queues, call apex-workspace-findings with company, workspaceRef, and findingState "open"; this direct selection does not require or change a local binding.
|
|
24
25
|
8. For PR scans, call apex-scan with mode "pr" and one or more pullRequests.
|
|
25
26
|
9. Send findings to Linear/Jira with apex-finding-send to create tickets using the frontend's saved destination. Link existing tickets with apex-finding-link-ticket and inspect links with apex-finding-tickets. The company integration must already be connected.
|
|
26
|
-
10.
|
|
27
|
+
10. Close or reopen findings with apex-finding-status using status fixed or open. This changes remediation, not scanner validity or feedback. Leave comments or valid/invalid feedback with apex-finding-comment and apex-finding-feedback when review collaboration is needed.
|
|
27
28
|
11. To review a fix, call apex-finding-fix-review. Pass fixPrUrls for PR-based fix reviews, or omit them to review the current repository state when Bedrock has original scan repository context.
|
|
28
29
|
|
|
29
30
|
Notes:
|
|
@@ -32,7 +33,7 @@ Notes:
|
|
|
32
33
|
- Finding write tools use the same Apex device-login credentials as read tools. Never ask for browser cookies or CANTINA_AUTH_TOKEN. An error requiring CANTINA_AUTH_TOKEN proves the MCP server is outdated; update Apex CLI, re-run apex setup for this client, restart it, and retry.
|
|
33
34
|
- Organization service-key creation, listing, and revocation are intentionally scripted-CLI-only and are not exposed as MCP tools. Never ask a user to paste a service key into a prompt or tool argument. A hosted MCP client supplies it only as the HTTP Authorization header.
|
|
34
35
|
- Production agents that do not need local filesystem access can connect to https://ai.cantina.xyz/mcp over Streamable HTTP with Authorization: Bearer <service-key>. Serverless and cron applications can instead use the REST API documented at https://ai.cantina.xyz/docs/apex-api. This local stdio server continues to use device login.
|
|
35
|
-
- Use mode "audit" for audit scans; "ultra" remains accepted as a legacy alias. Use mode "lite" only for beta-enabled provider-backed Lite scans, and mode "pr" for GitHub pull request scans.
|
|
36
|
+
- Use mode "audit" for audit scans; "ultra" remains accepted as a legacy alias. Use mode "lite" only for beta-enabled provider-backed Lite scans, and mode "pr" for GitHub or company-enabled Forgejo pull request scans.
|
|
36
37
|
- Use only the public Bedrock scanId returned by Apex for scan inputs. Kernel execution IDs are private and are neither returned nor accepted as aliases.
|
|
37
38
|
- Starting a PR scan may run alongside active workspace scans and does not require force. Starting any non-PR scan still treats every active scan, including a PR scan, as a blocker; use force only when the user explicitly wants that overlap.
|
|
38
39
|
- Doctor is read-only for guest sessions and reports the local snapshot plan without reserving a workspace.
|
|
@@ -542,7 +543,7 @@ function registerTools(server, auditSetupEnabled = false) {
|
|
|
542
543
|
}, value => `Threat-model draft ${String(value.draftId)}: ${String(value.state)}.`, input));
|
|
543
544
|
server.registerTool("apex-scan", {
|
|
544
545
|
title: "Start Apex Scan",
|
|
545
|
-
description: "Start a new Apex scan for the provided cwd by default. Pass repoPaths to select exact files, directories, or HTTPS GitHub/GitLab repository URLs. Remote URLs use the default branch; files never include siblings. Use mode audit for audit scans, mode lite for beta-gated provider-backed Lite scans, and mode pr for GitHub pull request scans.",
|
|
546
|
+
description: "Start a new Apex scan for the provided cwd by default. Pass repoPaths to select exact files, directories, or HTTPS GitHub/GitLab or company-enabled Forgejo repository URLs. Remote URLs use the default branch; files never include siblings. Use mode audit for audit scans, mode lite for beta-gated provider-backed Lite scans, and mode pr for GitHub or company-enabled Forgejo pull request scans.",
|
|
546
547
|
inputSchema: {
|
|
547
548
|
cwd: z.string().optional(),
|
|
548
549
|
company: z.string().optional(),
|
|
@@ -755,6 +756,22 @@ function registerTools(server, auditSetupEnabled = false) {
|
|
|
755
756
|
parentCommentId,
|
|
756
757
|
scanId,
|
|
757
758
|
}));
|
|
759
|
+
server.registerTool("apex-finding-status", {
|
|
760
|
+
title: "Change Apex Finding Status",
|
|
761
|
+
description: "Close a finding with fixed or reopen it with open. Changes client remediation only, preserving scanner validity, severity, feedback, and linked ticket status. Invalid or duplicate findings cannot be reopened until their classification is reviewed. Uses device login. Hosted MCP exposes the same tool with findingId and explicit findings:write. Optional expectedUpdatedAt prevents overwriting a newer edit.",
|
|
762
|
+
inputSchema: {
|
|
763
|
+
cwd: z.string().optional(),
|
|
764
|
+
findingRef: z.string(),
|
|
765
|
+
status: z.enum(FINDING_LIFECYCLE_STATUSES),
|
|
766
|
+
note: z.string().trim().max(1000).optional(),
|
|
767
|
+
expectedUpdatedAt: z.iso.datetime({ offset: true }).optional(),
|
|
768
|
+
scanId: z.string().optional(),
|
|
769
|
+
},
|
|
770
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
771
|
+
}, async ({ cwd, findingRef, status, note, expectedUpdatedAt, scanId }) => runTool("apex-finding-status", async () => {
|
|
772
|
+
const targetCwd = resolveCwd(cwd);
|
|
773
|
+
return { cwd: targetCwd, ...await commandFindingStatus(new ApexApiClient(), targetCwd, buildFlags({ scanId }), findingRef, status, note, expectedUpdatedAt) };
|
|
774
|
+
}, (value) => `Set finding ${String(value.findingRef)} to ${String(value.status)}.`, { cwd, findingRef, status, note, expectedUpdatedAt, scanId }));
|
|
758
775
|
server.registerTool("apex-finding-feedback", {
|
|
759
776
|
title: "Leave Apex Finding Feedback",
|
|
760
777
|
description: "Leave valid or invalid feedback on an Apex finding using the current Apex login. To attach a fix PR, send status valid with label fixed and fixPrUrls.",
|
|
@@ -868,11 +885,11 @@ function registerTools(server, auditSetupEnabled = false) {
|
|
|
868
885
|
}));
|
|
869
886
|
server.registerTool("apex-connect-provider", {
|
|
870
887
|
title: "Get Apex Provider Connection URL",
|
|
871
|
-
description: "Return the browser URL a user needs to connect GitHub or
|
|
888
|
+
description: "Return the browser URL a user needs to connect GitHub, GitLab or company-enabled Forgejo access for Apex. This tool never opens a browser.",
|
|
872
889
|
inputSchema: {
|
|
873
890
|
cwd: z.string().optional(),
|
|
874
891
|
company: z.string().optional(),
|
|
875
|
-
provider: z.enum(["github", "gitlab"]),
|
|
892
|
+
provider: z.enum(["github", "gitlab", "forgejo"]),
|
|
876
893
|
},
|
|
877
894
|
}, async ({ cwd, company, provider }) => runTool("apex-connect-provider", async () => {
|
|
878
895
|
const client = new ApexApiClient();
|
package/dist/repo-discovery.js
CHANGED
|
@@ -60,13 +60,13 @@ function inspectRemoteTarget(value) {
|
|
|
60
60
|
url = new URL(target);
|
|
61
61
|
}
|
|
62
62
|
catch {
|
|
63
|
-
throw new Error("Remote scan targets must be HTTPS GitHub or
|
|
63
|
+
throw new Error("Remote scan targets must be HTTPS GitHub, GitLab or company-enabled Forgejo repository URLs.");
|
|
64
64
|
}
|
|
65
65
|
const segments = url.pathname.replace(/\/+$/, "").split("/").filter(Boolean);
|
|
66
|
-
if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash ||
|
|
67
|
-
|
|
66
|
+
if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash ||
|
|
67
|
+
segments.length < 2 ||
|
|
68
68
|
(url.hostname === "github.com" && segments.length !== 2) || segments.includes("-")) {
|
|
69
|
-
throw new Error("Remote scan targets must be HTTPS GitHub or
|
|
69
|
+
throw new Error("Remote scan targets must be HTTPS GitHub, GitLab or company-enabled Forgejo repository URLs without credentials, query strings, or file/tree paths.");
|
|
70
70
|
}
|
|
71
71
|
const metadata = extractCanonicalRepoMetadata(url.href);
|
|
72
72
|
if (!metadata)
|
|
@@ -183,5 +183,5 @@ export async function discoverSources(cwd, selectedPaths = []) {
|
|
|
183
183
|
export function extractLegacyRepositories(sources) {
|
|
184
184
|
return sources.filter((source) => source.kind === "git_candidate" &&
|
|
185
185
|
typeof source.repoUrl === "string" &&
|
|
186
|
-
(source.provider === "github" || source.provider === "gitlab"));
|
|
186
|
+
(source.provider === "github" || source.provider === "gitlab" || source.provider === "forgejo"));
|
|
187
187
|
}
|
package/dist/repo-url.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseForgejoRepository } from "./forgejo-url.js";
|
|
1
2
|
function normalizeGitlabBaseUrl(value) {
|
|
2
3
|
try {
|
|
3
4
|
const url = new URL(value.trim());
|
|
@@ -99,6 +100,15 @@ export function extractGitlabProjectPathFromRepoUrl(repoUrl) {
|
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
export function extractCanonicalRepoMetadata(value) {
|
|
103
|
+
try {
|
|
104
|
+
const candidate = new URL(value.replace(/^git@([^:]+):/, "https://$1/"));
|
|
105
|
+
if (candidate.hostname === "codeberg.org" || candidate.hostname === "code.forgejo.org" || candidate.hostname.split(".").includes("forgejo")) {
|
|
106
|
+
const repo = parseForgejoRepository(candidate.href, candidate.origin);
|
|
107
|
+
if (repo)
|
|
108
|
+
return { provider: "forgejo", repoUrl: repo.normalizedUrl + ".git", repoName: repo.repoName, baseUrl: repo.baseUrl };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch { /* Local owner/repository shorthand is handled below. */ }
|
|
102
112
|
const githubRepoUrl = normalizeGithubRepoUrl(value);
|
|
103
113
|
if (githubRepoUrl?.startsWith("https://github.com/")) {
|
|
104
114
|
try {
|
package/dist/shell.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { commandStartThreatModel, hasAuditSetupBeta } from "./threat-model.js";
|
|
2
2
|
import { shellHelpText } from "./help.js";
|
|
3
|
-
import { commandCancelScan, commandClaim, commandConnect, commandCredits, commandDoctor, commandExportFindings, commandFindingComment, commandFindingTickets, commandFindingLinkTicket, commandFindingSend, commandFindingFeedback, commandFindingFixReview, commandFindings, commandWorkspaceFindings, commandLogout, commandScan, commandScans, commandStatus, commandTelemetry, commandUpdate, commandWorkspace, commandWorkspaceUse, commandWorkspaces, initializeInteractiveSession, openCurrentApexView, } from "./commands.js";
|
|
3
|
+
import { commandCancelScan, commandClaim, commandConnect, commandCredits, commandDoctor, commandExportFindings, commandFindingComment, commandFindingStatus, commandFindingTickets, commandFindingLinkTicket, commandFindingSend, commandFindingFeedback, commandFindingFixReview, commandFindings, commandWorkspaceFindings, commandLogout, commandScan, commandScans, commandStatus, commandTelemetry, commandUpdate, commandWorkspace, commandWorkspaceUse, commandWorkspaces, initializeInteractiveSession, openCurrentApexView, } from "./commands.js";
|
|
4
4
|
import { withFlag } from "./args.js";
|
|
5
5
|
import { printCompanyDetails, printInteractiveSessionSummary, printSourceList, printWorkspaceDetails, } from "./output.js";
|
|
6
6
|
import { readLine } from "./prompt.js";
|
|
@@ -13,7 +13,7 @@ const SHELL_COMPLETIONS = [
|
|
|
13
13
|
{ command: "scans" },
|
|
14
14
|
{
|
|
15
15
|
command: "findings",
|
|
16
|
-
args: ["workspace", "comment", "feedback", "fix-review", "tickets", "link-ticket", "send"],
|
|
16
|
+
args: ["workspace", "status", "comment", "feedback", "fix-review", "tickets", "link-ticket", "send"],
|
|
17
17
|
},
|
|
18
18
|
{ command: "export" },
|
|
19
19
|
{ command: "workspaces" },
|
|
@@ -27,7 +27,7 @@ const SHELL_COMPLETIONS = [
|
|
|
27
27
|
{ command: "repos" },
|
|
28
28
|
{ command: "workspace", args: ["use", "name"] },
|
|
29
29
|
{ command: "company" },
|
|
30
|
-
{ command: "connect", args: ["github", "gitlab"] },
|
|
30
|
+
{ command: "connect", args: ["github", "gitlab", "forgejo"] },
|
|
31
31
|
{ command: "open" },
|
|
32
32
|
{ command: "clear" },
|
|
33
33
|
{ command: "help" },
|
|
@@ -254,6 +254,12 @@ async function runShellCommand(client, cwd, parsed, shellFlags, session) {
|
|
|
254
254
|
await commandScans(client, cwd, shellFlags);
|
|
255
255
|
return {};
|
|
256
256
|
case "findings": {
|
|
257
|
+
if (parsed.args[0] === "status") {
|
|
258
|
+
if (parsed.args.length < 3)
|
|
259
|
+
throw new Error("Usage: /findings status <finding-ref> <open|fixed|acknowledged|in_review|pending_merge|false_positive|wont_fix|skipped> [note]");
|
|
260
|
+
await commandFindingStatus(client, cwd, shellFlags, parsed.args[1], parsed.args[2], parsed.args.slice(3).join(" ") || undefined);
|
|
261
|
+
return {};
|
|
262
|
+
}
|
|
257
263
|
if (parsed.args[0] === "send") {
|
|
258
264
|
if (parsed.args.length !== 3)
|
|
259
265
|
throw new Error("Usage: /findings send <finding-ref> <linear|jira>");
|
|
@@ -426,8 +432,8 @@ async function runShellCommand(client, cwd, parsed, shellFlags, session) {
|
|
|
426
432
|
}
|
|
427
433
|
case "connect": {
|
|
428
434
|
const provider = parsed.args[0];
|
|
429
|
-
if (provider !== "github" && provider !== "gitlab") {
|
|
430
|
-
process.stderr.write("Usage: /connect <github|gitlab>\n");
|
|
435
|
+
if (provider !== "github" && provider !== "gitlab" && provider !== "forgejo") {
|
|
436
|
+
process.stderr.write("Usage: /connect <github|gitlab|forgejo>\n");
|
|
431
437
|
return {};
|
|
432
438
|
}
|
|
433
439
|
await commandConnect(client, cwd, shellFlags, provider);
|
package/dist/telemetry.js
CHANGED
|
@@ -256,7 +256,7 @@ function sanitizeShellArgs(parsed) {
|
|
|
256
256
|
};
|
|
257
257
|
case "connect":
|
|
258
258
|
return {
|
|
259
|
-
provider: safeEnum(parsed.args[0] ?? null, ["github", "gitlab"]),
|
|
259
|
+
provider: safeEnum(parsed.args[0] ?? null, ["github", "gitlab", "forgejo"]),
|
|
260
260
|
};
|
|
261
261
|
case "findings": {
|
|
262
262
|
const action = parsed.args[0] ?? null;
|
|
@@ -283,7 +283,7 @@ function sanitizeShellArgs(parsed) {
|
|
|
283
283
|
commentLengthBucket: bucketLength(parsed.args.slice(feedbackStatus === "invalid" ? 4 : 3).join(" ")),
|
|
284
284
|
};
|
|
285
285
|
}
|
|
286
|
-
if (["fix-review", "tickets", "link-ticket", "send"].includes(action)) {
|
|
286
|
+
if (["status", "fix-review", "tickets", "link-ticket", "send"].includes(action)) {
|
|
287
287
|
return {
|
|
288
288
|
findingRefProvided: Boolean(parsed.args[1]),
|
|
289
289
|
};
|
|
@@ -327,11 +327,11 @@ function safeCliSubcommand(parsed) {
|
|
|
327
327
|
return null;
|
|
328
328
|
switch (parsed.command) {
|
|
329
329
|
case "connect":
|
|
330
|
-
return safeEnum(subcommand, ["github", "gitlab"]);
|
|
330
|
+
return safeEnum(subcommand, ["github", "gitlab", "forgejo"]);
|
|
331
331
|
case "export":
|
|
332
332
|
return safeEnum(subcommand, ["findings"]);
|
|
333
333
|
case "findings":
|
|
334
|
-
return safeEnum(subcommand, ["comment", "feedback", "fix-review", "tickets", "link-ticket", "send"]);
|
|
334
|
+
return safeEnum(subcommand, ["comment", "feedback", "status", "fix-review", "tickets", "link-ticket", "send"]);
|
|
335
335
|
case "setup":
|
|
336
336
|
return safeEnum(subcommand, ["all", "codex", "claude", "copilot"]);
|
|
337
337
|
case "service-key":
|
|
@@ -404,6 +404,7 @@ export function sanitizeMcpInput(input = {}) {
|
|
|
404
404
|
provider: safeEnum(typeof input.provider === "string" ? input.provider : null, [
|
|
405
405
|
"github",
|
|
406
406
|
"gitlab",
|
|
407
|
+
"forgejo",
|
|
407
408
|
]),
|
|
408
409
|
ticketProvider: safeEnum(typeof input.provider === "string" ? input.provider : null, ["linear", "jira"]),
|
|
409
410
|
feedbackStatus: safeEnum(typeof input.status === "string" ? input.status : null, [
|
package/dist/types.js
CHANGED
package/package.json
CHANGED
package/skills/apex-cli/SKILL.md
CHANGED
|
@@ -9,7 +9,7 @@ This skill is bundled with Apex CLI and can be installed into Codex with `apex s
|
|
|
9
9
|
|
|
10
10
|
Prefer the Apex MCP tools over running `apex` in the shell when the server is available. The tools installed by `apex setup` use the local stdio server and device login.
|
|
11
11
|
|
|
12
|
-
For a production MCP client that does not need local filesystem access, use the hosted Streamable HTTP endpoint at `https://ai.cantina.xyz/mcp`. Read the organization service key from `APEX_SERVICE_KEY` and supply it only as `Authorization: Bearer <TOKEN>`; never put the raw key in a tool argument, result, prompt, source file, or log. The hosted tools are `apex-credits`, `apex-workspaces`, `apex-scan`, `apex-black-start`, `apex-status`, `apex-cancel-scan`, `apex-findings`, `apex-workspace-findings`, `apex-finding-tickets`, `apex-finding-link-ticket`, and `apex-finding-
|
|
12
|
+
For a production MCP client that does not need local filesystem access, use the hosted Streamable HTTP endpoint at `https://ai.cantina.xyz/mcp`. Read the organization service key from `APEX_SERVICE_KEY` and supply it only as `Authorization: Bearer <TOKEN>`; never put the raw key in a tool argument, result, prompt, source file, or log. The hosted tools are `apex-credits`, `apex-workspaces`, `apex-scan`, `apex-black-start`, `apex-status`, `apex-cancel-scan`, `apex-findings`, `apex-workspace-findings`, `apex-finding-tickets`, `apex-finding-link-ticket`, `apex-finding-send`, and `apex-finding-status`.
|
|
13
13
|
|
|
14
14
|
OAuth-capable clients may instead use the hosted endpoint’s OAuth discovery and Cantina manager-consent flow. Let the client manage OAuth credentials; never pass them to tools, the REST API, or local CLI. Local stdio MCP continues to use device login.
|
|
15
15
|
|
|
@@ -78,9 +78,9 @@ Guidelines:
|
|
|
78
78
|
- `apex-scan` scans the provided `cwd` by default; pass `repoPaths` only when the user asks to scan explicit alternate roots.
|
|
79
79
|
- `apex-doctor` is read-only for guest sessions and reports the local snapshot plan without reserving a workspace. For account sessions it reports whether Apex will use remote materialization or a local snapshot upload for each selected source.
|
|
80
80
|
- Apex can scan plain local directories and dirty git worktrees without provider connections by using local snapshot uploads.
|
|
81
|
-
- Audit scans use `mode: "audit"` in user-facing instructions and request payloads. The legacy `ultra` mode remains accepted as an alias, but audit scans still require provider-backed GitHub or
|
|
81
|
+
- Audit scans use `mode: "audit"` in user-facing instructions and request payloads. The legacy `ultra` mode remains accepted as an alias, but audit scans still require provider-backed GitHub, GitLab or company-enabled Forgejo sources.
|
|
82
82
|
- Lite scans use `mode: "lite"`, require Lite Scan beta access, and require provider-backed GitHub or GitLab sources without local snapshot fallback.
|
|
83
|
-
- PR scans use `mode: "pr"` and require one provider-backed GitHub repository plus one or more `pullRequests`.
|
|
83
|
+
- PR scans use `mode: "pr"` and require one provider-backed GitHub or company-enabled, connected Forgejo repository plus one or more `pullRequests`.
|
|
84
84
|
- Starting a PR scan may run alongside active workspace scans and does not require `force`. Starting any non-PR scan still treats every active scan, including a PR scan, as a blocker; use `force: true` only when the user explicitly wants that overlap.
|
|
85
85
|
- `apex-workspace-use` accepts a workspace name, prefix, or ID.
|
|
86
86
|
- Use `sourceMode: "remote"` only when the user explicitly wants to forbid local snapshot fallbacks.
|
|
@@ -155,8 +155,23 @@ values. If Audit Setup or another workspace update is active, wait for it to
|
|
|
155
155
|
finish before saving again.
|
|
156
156
|
|
|
157
157
|
Scan target and recovery rules:
|
|
158
|
-
- CLI `apex scan [target...]` and repeatable `--repo` accept exact files, directories, or HTTPS GitHub/GitLab repository URLs; local MCP uses `repoPaths` for the same selections. No target means `cwd`.
|
|
158
|
+
- CLI `apex scan [target...]` and repeatable `--repo` accept exact files, directories, or HTTPS GitHub/GitLab or company-enabled Forgejo repository URLs; local MCP uses `repoPaths` for the same selections. No target means `cwd`.
|
|
159
159
|
- A file selection scans only that file, respecting ignore rules. Local snapshots exclude `.apex/` state. Remote URLs use the default branch and cannot fall back to uploading `cwd`; clone locally to choose a branch or commit.
|
|
160
160
|
- Use Standard for local changes, files, plain directories, and public repositories without provider access. Lite and Audit still require connected provider sources; do not silently substitute another mode.
|
|
161
161
|
- Unauthenticated headless CLI commands fail with explicit login guidance; they never start device login. Use the MCP auth tools for explicit MCP login. Retry transient refresh failures without clearing credentials.
|
|
162
162
|
- Audit Setup is an explicit Standard/Audit beta flow using saved workspace repositories and context. It does not preserve local targets. Never automatically substitute setup after `AUDIT_SETUP_REQUIRED`; use the error's selected-workspace command only when that saved scope is intended. Draft operations require the ID returned by preparation; eligible accounts receive setup commands in CLI help. Lite and PR do not use `--setup`.
|
|
163
|
+
|
|
164
|
+
Finding lifecycle:
|
|
165
|
+
- Close resolved issues with `apex-finding-status` and `status: "fixed"`; reopen with `status: "open"`.
|
|
166
|
+
- Local MCP takes `findingRef`, optional `cwd`/`scanId`, `note`, and `expectedUpdatedAt`, using device login. Hosted MCP takes `findingId` (UUID), no local paths, and requires explicit `findings:write`.
|
|
167
|
+
- The scripted equivalent is `apex findings status <finding-ref> <open|fixed> --note "rationale" --json`. Other statuses: acknowledged, in_review, pending_merge, false_positive, wont_fix, skipped.
|
|
168
|
+
- Status changes preserve scanner validity, severity, feedback, and linked ticket state. Invalid or duplicate findings cannot be reopened into the open queue until their classification is reviewed.
|
|
169
|
+
- Read `remediationStatus` in workspace finding lists for the current lifecycle; `status` remains the scanner verdict. `open` and `acknowledged` stay distinct even though both use the `accepted` review state.
|
|
170
|
+
- Use the finding updatedAt as expectedUpdatedAt when working from a prior snapshot. On FINDING_STATUS_CONFLICT, reload and review before retrying.
|
|
171
|
+
- Linear sync is Linear-to-Apex: started becomes acknowledged, completed becomes fixed. Do not promise Apex-to-Linear status updates or automatic Linear reopening.
|
|
172
|
+
|
|
173
|
+
## Forgejo
|
|
174
|
+
|
|
175
|
+
Forgejo connections, remote repository scans, PR scans and Fix Review require the Forgejo company feature enabled by an administrator in Bedrock Settings. Run `apex connect forgejo` (or `apex-connect-provider` with `provider: "forgejo"`) to obtain the browser connection URL. A dedicated bot, HTTPS instance URL and repository-scoped read token are required; PR statuses, comments and webhook installation also require a write token.
|
|
176
|
+
|
|
177
|
+
After connecting, run `apex doctor --repo . --source-mode remote`, then `apex scan --mode standard --repo . --source-mode remote` or `apex scan --mode pr --pr 42 --repo .`. Same-repository Forgejo PR URLs use `/owner/repository/pulls/42`. Pass a Forgejo Fix PR URL through finding feedback and start Fix Review using the existing finding commands or MCP tools. Disabling the company flag stops native Forgejo access. Existing local Standard snapshots remain provider-independent.
|