@cantinasecurity/apex-cli 0.1.21 → 0.1.23
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 +5 -4
- 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 +48 -14
- package/dist/commands.js +31 -3
- package/dist/findings.js +9 -4
- package/dist/help.js +8 -3
- package/dist/mcp.js +21 -7
- package/dist/setup.js +100 -22
- package/dist/telemetry.js +5 -0
- package/package.json +1 -1
- package/skills/apex-cli/SKILL.md +4 -3
|
@@ -37,7 +37,7 @@ Recommended workflow:
|
|
|
37
37
|
4. For repository-scoped work, pass `cwd` explicitly.
|
|
38
38
|
5. Call `apex-doctor` before starting a scan if workspace binding or source planning might be unclear.
|
|
39
39
|
6. If the directory is not bound to the right workspace, call `apex-workspaces` and `apex-workspace-use`.
|
|
40
|
-
7. Use `apex-scan`, `apex-status`, `apex-scans`, `apex-findings`, and `apex-export-findings` for the scan lifecycle. Use `apex-workspace-findings` for the cross-scan workspace browser view.
|
|
40
|
+
7. Use `apex-scan`, `apex-status`, `apex-scans`, `apex-findings`, and `apex-export-findings` for the scan lifecycle. Use `apex-workspace-findings` for the cross-scan workspace browser view. To review unresolved findings across any accessible workspace, first call `apex-workspaces` with the company, then call `apex-workspace-findings` with `company`, `workspaceRef`, and `findingState: "open"`; this does not change the local binding.
|
|
41
41
|
8. For Lite scans, call `apex-scan` with `mode: "lite"` only when the company has Lite Scan beta access and the sources are provider-backed.
|
|
42
42
|
9. For PR scans, call `apex-scan` with `mode: "pr"` and `pullRequests`.
|
|
43
43
|
10. Use `apex-finding-comment` and `apex-finding-feedback` when the user wants to leave review notes or valid/invalid feedback on a finding.
|
|
@@ -69,8 +69,9 @@ If the Apex MCP server is not configured, fall back to the local CLI:
|
|
|
69
69
|
- `apex-workspace-use` accepts a workspace name, prefix, or ID.
|
|
70
70
|
- Use `sourceMode: "remote"` only when the user explicitly wants to forbid local snapshot fallbacks.
|
|
71
71
|
- When checking a scan that is not the workspace binding's latest scan, pass `scanId` to `apex-status`; use `apex scans` or `apex-scans` first if you need to discover scan IDs.
|
|
72
|
-
- Use `apex-workspace-findings` when the user asks for findings across a workspace instead of one scan.
|
|
73
|
-
-
|
|
72
|
+
- Use `apex-workspace-findings` when the user asks for findings across a workspace instead of one scan. Set `findingState: "open"` for findings needing review; open excludes fixed, invalid, dismissed, and duplicate findings and is not `status:open`. Pass `company` plus a workspace name, prefix, or ID as `workspaceRef` to avoid relying on or mutating a local binding. In CLI-only flows, use `apex findings --workspace --company <handle> --workspace-ref <ref> --finding-state open`.
|
|
73
|
+
- Workspace finding responses expose the web UI lifecycle fields `status`, `validity`, `reviewState`, `isDuplicate`, `duplicateOfId`, `repository`, and `sourceFile`. Supported filter facets are `severity`, `scan`, `repo`, `review`, `impact`, `likelihood`, `status`, `validation`, `visibility`, `duplicate`, and `source`; a leading `!` negates a filter.
|
|
74
|
+
- Finding comments, feedback, and fix review scan starts use the same Apex device-login credentials as read tools. If a write tool reports missing auth, re-run `apex-auth-status`, check `apex-trial-status`, and complete `apex-auth-start` / `apex-auth-wait` only when no unexpired local claim proof is available. Never ask for browser cookies or `CANTINA_AUTH_TOKEN`; an error requiring that variable proves the MCP server is outdated, so update Apex CLI, re-run `apex setup <client>`, restart the client, and retry.
|
|
74
75
|
- Anonymous telemetry records only sanitized command/tool metadata such as command names, enum modes, counts, durations, success/failure categories, CLI version, and client integration. It must not include raw repository paths, scan IDs, finding IDs, comments, file paths, PR URLs, trial codes, claim tokens, onboarding fingerprints, or bearer tokens.
|
|
75
76
|
- Invalid finding feedback requires `dismissalReason`; valid feedback can include `suggestedSeverity`, including `extreme`.
|
|
76
77
|
- PR-based fix reviews can pass `fixPrUrls` directly to `apex-finding-fix-review`; agents that also need to record fixed feedback can first call `apex-finding-feedback` with `status: "valid"`, `labels: ["fixed"]`, and `fixPrUrls`.
|
|
@@ -85,6 +86,6 @@ If the Apex MCP server is not configured, fall back to the local CLI:
|
|
|
85
86
|
- Start a PR scan: call `apex-scan` with `mode: "pr"` and `pullRequests`.
|
|
86
87
|
- Check an active scan: call `apex-status`, passing `scanId` when the scan is not the latest binding, then `apex-findings` when the scan completes.
|
|
87
88
|
- Export findings for review: call `apex-export-findings` with `format` set to `markdown`, `json`, or `gitlab-sast`.
|
|
88
|
-
- Review all open workspace findings: call `apex-workspace-findings` with `
|
|
89
|
+
- Review all open workspace findings: call `apex-workspaces` with the company, then call `apex-workspace-findings` with the selected `workspaceRef` and `findingState: "open"`.
|
|
89
90
|
- Leave review feedback: call `apex-finding-comment` to add a note, or `apex-finding-feedback` with `status` set to `valid` or `invalid`.
|
|
90
91
|
- Run a Fix PR review: call `apex-finding-fix-review` with `fixPrUrls`, optionally after recording fixed feedback with `apex-finding-feedback`.
|
|
@@ -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.23"
|
|
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.23"
|
|
18
18
|
},
|
|
19
19
|
"description": "Run Apex security scans and review findings from Claude Code.",
|
|
20
|
-
"version": "0.1.
|
|
20
|
+
"version": "0.1.23",
|
|
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.23 apex-mcp
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
That keeps marketplace installs independent of a user's global `apex` install.
|
package/README.md
CHANGED
|
@@ -54,7 +54,11 @@ git pull --ff-only
|
|
|
54
54
|
pnpm install
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
For published installs, setup-managed MCP registrations launch the current npm
|
|
58
|
+
release, so restarting the client picks up the current server instead of keeping
|
|
59
|
+
an old global `apex-mcp` process indefinitely. Re-run `apex setup` after
|
|
60
|
+
upgrading to refresh copied skill files, and run `apex setup claude` in each
|
|
61
|
+
repository where you want the Claude project skill.
|
|
58
62
|
|
|
59
63
|
Restart any already-running MCP client after an Apex CLI upgrade before using
|
|
60
64
|
trial redemption or claim. A process that loaded an older credential writer
|
|
@@ -141,30 +145,30 @@ unclaimed local trial proof so `apex claim` can still use it.
|
|
|
141
145
|
Agent-safe copy/paste prompt (substitute the issued code):
|
|
142
146
|
|
|
143
147
|
```text
|
|
144
|
-
Install and run Cantina Apex CLI 0.1.
|
|
148
|
+
Install and run Cantina Apex CLI 0.1.23 to start one free standard security scan of this repository immediately.
|
|
145
149
|
|
|
146
150
|
Use this trial code only where <TRIAL_CODE> appears below: SPBT-DEVREL-XXXXXXXX
|
|
147
151
|
|
|
148
152
|
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`.
|
|
149
153
|
|
|
150
154
|
Use this exact command prefix for every Apex command:
|
|
151
|
-
npx -y -p @cantinasecurity/apex-cli@0.1.
|
|
155
|
+
npx -y -p @cantinasecurity/apex-cli@0.1.23 apex
|
|
152
156
|
|
|
153
157
|
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.
|
|
154
158
|
2. Redeem, with at most one response-loss recovery retry:
|
|
155
|
-
npx -y -p @cantinasecurity/apex-cli@0.1.
|
|
159
|
+
npx -y -p @cantinasecurity/apex-cli@0.1.23 apex redeem <TRIAL_CODE> --json
|
|
156
160
|
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. Continue when the command exits 0 and stdout is JSON with `redeemed: true` and `outcome` equal to `redeemed` or `idempotent`. 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.
|
|
157
161
|
3. Immediately start exactly one scan; do not wait for another instruction:
|
|
158
|
-
npx -y -p @cantinasecurity/apex-cli@0.1.
|
|
162
|
+
npx -y -p @cantinasecurity/apex-cli@0.1.23 apex scan --mode standard --non-interactive --no-open --json
|
|
159
163
|
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.
|
|
160
164
|
4. About every 60 seconds, poll that exact scan:
|
|
161
|
-
npx -y -p @cantinasecurity/apex-cli@0.1.
|
|
165
|
+
npx -y -p @cantinasecurity/apex-cli@0.1.23 apex status --scan <SCAN_ID> --json
|
|
162
166
|
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.
|
|
163
167
|
5. Fetch that exact scan's findings:
|
|
164
|
-
npx -y -p @cantinasecurity/apex-cli@0.1.
|
|
168
|
+
npx -y -p @cantinasecurity/apex-cli@0.1.23 apex findings --scan <SCAN_ID> --json
|
|
165
169
|
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.
|
|
166
170
|
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:
|
|
167
|
-
npx -y -p @cantinasecurity/apex-cli@0.1.
|
|
171
|
+
npx -y -p @cantinasecurity/apex-cli@0.1.23 apex claim
|
|
168
172
|
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.
|
|
169
173
|
|
|
170
174
|
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.
|
|
@@ -235,7 +239,7 @@ Supported shell commands:
|
|
|
235
239
|
- `apex scan --mode pr --pr <number> [--pr <number>] [--pr-path <path>]`
|
|
236
240
|
- `apex scans`
|
|
237
241
|
- `apex findings [--scan <scan-id>]`
|
|
238
|
-
- `apex findings --workspace [--filter <[!]facet:value>]`
|
|
242
|
+
- `apex findings --workspace [--company <handle>] [--workspace-ref <name|prefix|id>] [--finding-state all|open] [--filter <[!]facet:value>]`
|
|
239
243
|
- `apex findings comment <finding-id|finding-identifier> --content <markdown> [--parent-comment <comment-id>] [--scan <scan-id>]`
|
|
240
244
|
- `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 <github-pr-url>]`
|
|
241
245
|
- `apex findings fix-review <finding-id|finding-identifier> [--scan <scan-id>] [--review-target pull-request|repository-current] [--fix-pr-url <github-pr-url>]`
|
|
@@ -354,6 +358,10 @@ Identifiers such as `KERN2-25` are resolved against the selected or latest scan
|
|
|
354
358
|
|
|
355
359
|
Finding comments, valid/invalid feedback, and fix review scan starts use the same Apex login credentials as read commands. In MCP clients, call `apex-auth-start`, then pass both its returned `deviceCode` and opaque `credentialGeneration` to `apex-auth-wait` to authenticate these write tools safely.
|
|
356
360
|
|
|
361
|
+
Never copy a browser cookie or set `CANTINA_AUTH_TOKEN` for these tools. If an
|
|
362
|
+
MCP server asks for that variable, it is an outdated pre-device-auth server.
|
|
363
|
+
Update Apex CLI, re-run `apex setup <client>`, restart the client, and retry.
|
|
364
|
+
|
|
357
365
|
Invalid feedback requires a dismissal reason. Valid feedback can include `--suggested-severity extreme|critical|high|medium|low|informational`.
|
|
358
366
|
|
|
359
367
|
Fix review scans can review either a Fix PR or the current repository state:
|
|
@@ -419,6 +427,11 @@ claude mcp add --scope user \
|
|
|
419
427
|
-- apex apex-mcp
|
|
420
428
|
```
|
|
421
429
|
|
|
430
|
+
`apex setup claude` checks whether the installed Claude CLI supports
|
|
431
|
+
`--scope user`. On older Claude versions that reject that option, setup safely
|
|
432
|
+
merges the same user-level `mcpServers.apex` entry into `~/.claude.json`
|
|
433
|
+
without replacing other settings or servers.
|
|
434
|
+
|
|
422
435
|
For GitHub Copilot CLI:
|
|
423
436
|
|
|
424
437
|
```bash
|
|
@@ -486,7 +499,7 @@ The MCP server exposes Apex-specific tools for:
|
|
|
486
499
|
- doctor, credits, and provider connection URLs
|
|
487
500
|
- workspace inspection and workspace binding
|
|
488
501
|
- scan start, status, cancellation, findings, and findings export
|
|
489
|
-
- cross-scan workspace findings through `apex-workspace-findings`,
|
|
502
|
+
- cross-scan workspace findings through `apex-workspace-findings`, including direct workspace selection, first-class open state, web UI lifecycle fields, and server-side filters
|
|
490
503
|
- Lite scan start by calling `apex-scan` with `mode: "lite"`
|
|
491
504
|
- PR scan start by calling `apex-scan` with `mode: "pr"` and `pullRequests`
|
|
492
505
|
- finding comments and valid/invalid feedback with `apex-finding-comment` and `apex-finding-feedback`
|
|
@@ -496,7 +509,28 @@ The MCP server does not expose trial redemption, trial claim, or service-key cre
|
|
|
496
509
|
|
|
497
510
|
For repository-scoped operations, pass `cwd` explicitly so the server can resolve the right `.apex/workspace.json` binding and repository roots.
|
|
498
511
|
|
|
499
|
-
Use `apex-workspace-findings` when an agent needs the same cross-scan finding
|
|
512
|
+
Use `apex-workspace-findings` when an agent needs the same cross-scan finding
|
|
513
|
+
set as the workspace browser. Pass `company`, `workspaceRef`, and
|
|
514
|
+
`findingState: "open"` to select any accessible workspace without changing the
|
|
515
|
+
local binding and return only findings that still need review:
|
|
516
|
+
|
|
517
|
+
```json
|
|
518
|
+
{
|
|
519
|
+
"company": "apex-internal",
|
|
520
|
+
"workspaceRef": "BEDR9",
|
|
521
|
+
"findingState": "open"
|
|
522
|
+
}
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
Open state excludes fixed, invalid, dismissed, and duplicate findings. It is a
|
|
526
|
+
first-class selection, not `status:open`; actual finding status values are
|
|
527
|
+
`proposed`, `partially_valid`, `valid`, `invalid`, and `duplicate`. The
|
|
528
|
+
response includes `status`, `validity`, `reviewState`, duplicate metadata,
|
|
529
|
+
repository, and source file fields used by the web UI. Supported filter facets
|
|
530
|
+
are `severity`, `scan`, `repo`, `review`, `impact`, `likelihood`, `status`,
|
|
531
|
+
`validation`, `visibility`, `duplicate`, and `source`. Repeated filters in one
|
|
532
|
+
facet are ORed; filters across different facets are ANDed, and a leading `!`
|
|
533
|
+
negates a filter.
|
|
500
534
|
|
|
501
535
|
For Codex-style clients, the packaged skill can be installed with `apex setup codex`. For GitHub Copilot CLI, the same skill is installed into `~/.copilot/skills/apex-cli` with `apex setup copilot`. The repo-local source lives at `skills/apex-cli/SKILL.md`.
|
|
502
536
|
|
|
@@ -533,7 +567,7 @@ Telemetry event posts do not include bearer tokens. The telemetry endpoint is `P
|
|
|
533
567
|
{
|
|
534
568
|
"schemaVersion": 1,
|
|
535
569
|
"event": "apex.invocation.completed",
|
|
536
|
-
"cliVersion": "0.1.
|
|
570
|
+
"cliVersion": "0.1.23",
|
|
537
571
|
"invocation": {
|
|
538
572
|
"surface": "mcp",
|
|
539
573
|
"command": "apex-scan",
|
|
@@ -564,7 +598,7 @@ The npm package also includes marketplace-ready plugin artifacts:
|
|
|
564
598
|
- `.claude-plugin/plugin.json` and `.mcp.claude.json` for Claude Code plugin installs
|
|
565
599
|
- `.claude-plugin/marketplace.json` for a Claude marketplace entry backed by the public npm package
|
|
566
600
|
|
|
567
|
-
These plugin installs launch the pinned npm package with `npx -y -p @cantinasecurity/apex-cli@0.1.
|
|
601
|
+
These plugin installs launch the pinned npm package with `npx -y -p @cantinasecurity/apex-cli@0.1.23 apex-mcp`, so users do not need to install `apex` globally before enabling the plugin.
|
|
568
602
|
|
|
569
603
|
The repository also includes `.agents/plugins/marketplace.json` for local Codex marketplace testing from a checkout.
|
|
570
604
|
|
|
@@ -600,4 +634,4 @@ To move between existing Apex workspaces from the CLI:
|
|
|
600
634
|
2. Run `apex workspace use <workspace-name|workspace-prefix|workspace-id>` to bind the current directory.
|
|
601
635
|
3. If the workspace name contains spaces, quote it, for example `apex workspace use "Core Platform"`.
|
|
602
636
|
4. Use `apex scans`, `apex status --scan <scan-id>`, `apex findings`, and `apex export findings` against that binding.
|
|
603
|
-
5. Use `apex findings --workspace --
|
|
637
|
+
5. Use `apex findings --workspace --company <handle> --workspace-ref <ref> --finding-state open` for the workspace browser's cross-scan findings that still need review, without changing the local binding.
|
package/dist/commands.js
CHANGED
|
@@ -1442,8 +1442,35 @@ export async function commandWorkspaceFindings(client, cwd, flags) {
|
|
|
1442
1442
|
const me = await ensureAuthenticated(client, flags);
|
|
1443
1443
|
const authState = await getCommandAuthState(client, me);
|
|
1444
1444
|
setTelemetryAuthKind(authState.authKind);
|
|
1445
|
-
const
|
|
1446
|
-
const
|
|
1445
|
+
const currentBinding = await loadWorkspaceBinding(cwd);
|
|
1446
|
+
const workspaceRef = getFlagString(flags, "workspace-ref");
|
|
1447
|
+
if (getFlagString(flags, "company") && !workspaceRef) {
|
|
1448
|
+
throw new Error("--company requires --workspace-ref for workspace findings so the selected company cannot be confused with the local binding.");
|
|
1449
|
+
}
|
|
1450
|
+
let workspaceId;
|
|
1451
|
+
let binding;
|
|
1452
|
+
if (workspaceRef) {
|
|
1453
|
+
const company = await chooseCompany(me, flags, currentBinding);
|
|
1454
|
+
const workspaces = await withLoadingIndicator("Loading workspaces...", flags, () => fetchCompanyWorkspaces(client, company.id));
|
|
1455
|
+
const workspace = findWorkspaceByRef(workspaces.workspaces, workspaceRef);
|
|
1456
|
+
if (!workspace) {
|
|
1457
|
+
throw new Error(`Could not find a workspace matching "${workspaceRef}". ${WORKSPACE_SELECTION_GUIDANCE}`);
|
|
1458
|
+
}
|
|
1459
|
+
workspaceId = workspace.workspaceId;
|
|
1460
|
+
binding =
|
|
1461
|
+
currentBinding?.workspaceId === workspace.workspaceId
|
|
1462
|
+
? currentBinding
|
|
1463
|
+
: null;
|
|
1464
|
+
}
|
|
1465
|
+
else {
|
|
1466
|
+
binding = requireLoadedWorkspaceBinding(currentBinding);
|
|
1467
|
+
workspaceId = binding.workspaceId;
|
|
1468
|
+
}
|
|
1469
|
+
const requestedState = getFlagString(flags, "finding-state") ?? "all";
|
|
1470
|
+
if (requestedState !== "all" && requestedState !== "open") {
|
|
1471
|
+
throw new Error("Workspace finding state must be all or open.");
|
|
1472
|
+
}
|
|
1473
|
+
const response = await withLoadingIndicator("Loading workspace findings...", flags, () => fetchWorkspaceFindings(client, workspaceId, getFlagList(flags, "filter"), requestedState));
|
|
1447
1474
|
const payload = {
|
|
1448
1475
|
...response,
|
|
1449
1476
|
binding,
|
|
@@ -1453,6 +1480,7 @@ export async function commandWorkspaceFindings(client, cwd, flags) {
|
|
|
1453
1480
|
return payload;
|
|
1454
1481
|
}
|
|
1455
1482
|
logLine(`Workspace: ${response.workspace.name}`, flags);
|
|
1483
|
+
logLine(`Finding state: ${response.state}`, flags);
|
|
1456
1484
|
if (response.filters.length > 0) {
|
|
1457
1485
|
logLine(`Filters: ${response.filters.join(", ")}`, flags);
|
|
1458
1486
|
}
|
|
@@ -1463,7 +1491,7 @@ export async function commandWorkspaceFindings(client, cwd, flags) {
|
|
|
1463
1491
|
}
|
|
1464
1492
|
for (const finding of response.findings) {
|
|
1465
1493
|
const identifier = finding.findingIdentifier ?? finding.id;
|
|
1466
|
-
logLine(`${finding.severity.toUpperCase()} ${identifier} ${finding.title} (${finding.status})`, flags);
|
|
1494
|
+
logLine(`${finding.severity.toUpperCase()} ${identifier} ${finding.title} (${finding.status}, ${finding.reviewState}, ${finding.validity})`, flags);
|
|
1467
1495
|
if (finding.findingUrl) {
|
|
1468
1496
|
logLine(`View: ${finding.findingUrl}`, flags);
|
|
1469
1497
|
}
|
package/dist/findings.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { fetchWorkspaceScans, getScanDisplayId, matchesScanId, } from "./scan.js";
|
|
2
2
|
import { ApiError, CredentialGenerationChangedError, isTerminalGuestSessionError, } from "./api-client.js";
|
|
3
3
|
const FINDING_UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
4
|
-
const WORKSPACE_FINDING_FILTER_PATTERN = /^!?(severity|scan|status|validation|visibility):[^:]+$/;
|
|
4
|
+
const WORKSPACE_FINDING_FILTER_PATTERN = /^!?(severity|scan|repo|review|impact|likelihood|status|validation|visibility|duplicate|source):[^:]+$/;
|
|
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";
|
|
@@ -185,16 +185,21 @@ export function normalizeWorkspaceFindingFilters(filters) {
|
|
|
185
185
|
}
|
|
186
186
|
return filters.map((filter) => {
|
|
187
187
|
const normalized = filter.trim();
|
|
188
|
+
const normalizedStatusFilter = normalized
|
|
189
|
+
.replace(/^!/, "")
|
|
190
|
+
.toLowerCase();
|
|
188
191
|
if (normalized.length === 0 ||
|
|
189
192
|
normalized.length > MAX_WORKSPACE_FINDING_FILTER_LENGTH ||
|
|
190
|
-
!WORKSPACE_FINDING_FILTER_PATTERN.test(normalized)
|
|
191
|
-
|
|
193
|
+
!WORKSPACE_FINDING_FILTER_PATTERN.test(normalized) ||
|
|
194
|
+
normalizedStatusFilter === "status:open") {
|
|
195
|
+
throw new Error("Invalid workspace finding filter. Use a supported web-UI facet such as severity:high, review:unreviewed, duplicate:unique, or !validation:invalid. `open` is a finding state, not a status filter.");
|
|
192
196
|
}
|
|
193
197
|
return normalized;
|
|
194
198
|
});
|
|
195
199
|
}
|
|
196
|
-
export async function fetchWorkspaceFindings(client, workspaceId, filters) {
|
|
200
|
+
export async function fetchWorkspaceFindings(client, workspaceId, filters, state = "all") {
|
|
197
201
|
const params = new URLSearchParams();
|
|
202
|
+
params.set("state", state);
|
|
198
203
|
for (const filter of normalizeWorkspaceFindingFilters(filters)) {
|
|
199
204
|
params.append("filter", filter);
|
|
200
205
|
}
|
package/dist/help.js
CHANGED
|
@@ -43,6 +43,8 @@ Flags:
|
|
|
43
43
|
--workspace-name <name> Set the Apex workspace name for this directory
|
|
44
44
|
--scan <scan-id> Select a specific scan for status, findings, or export
|
|
45
45
|
--workspace Use the cross-scan workspace findings view
|
|
46
|
+
--workspace-ref <name|prefix|id> Select a workspace without changing the local binding
|
|
47
|
+
--finding-state all|open Select all findings or only findings needing review
|
|
46
48
|
--filter <[!]facet:value> Filter workspace findings; repeat for multiple filters
|
|
47
49
|
--status valid|invalid Set the finding feedback status explicitly
|
|
48
50
|
--content <markdown> Supply comment content for apex findings comment
|
|
@@ -92,7 +94,9 @@ Tips:
|
|
|
92
94
|
Invalid finding feedback requires --dismissal-reason.
|
|
93
95
|
Fix review scans use saved or supplied Fix PR URLs when present; otherwise they review the current repository when Bedrock has original scan repository context.
|
|
94
96
|
Finding identifiers such as KERN2-25 resolve against the selected scan; pass --scan or use the finding UUID directly when needed.
|
|
95
|
-
Use apex findings --workspace --
|
|
97
|
+
Use apex findings --workspace --company <handle> --workspace-ref <ref> --finding-state open to retrieve findings needing review without changing the local binding.
|
|
98
|
+
Open excludes fixed, invalid, dismissed, and duplicate findings. It is a finding state, not a status filter.
|
|
99
|
+
Workspace filters match the web UI facets: severity, scan, repo, review, impact, likelihood, status, validation, visibility, duplicate, and source.
|
|
96
100
|
Anonymous usage telemetry helps Apex understand CLI, MCP, Codex, Claude, and Copilot usage. Disable it with apex telemetry disable or APEX_TELEMETRY_DISABLED=1.
|
|
97
101
|
Quote workspace names that contain spaces:
|
|
98
102
|
apex workspace use "Core Platform"
|
|
@@ -107,7 +111,7 @@ Commands:
|
|
|
107
111
|
/scans List scans for this workspace
|
|
108
112
|
/findings [scan-id] List findings for the latest or selected scan
|
|
109
113
|
/findings workspace [filter...]
|
|
110
|
-
List workspace findings; for example
|
|
114
|
+
List workspace findings; for example review:unreviewed
|
|
111
115
|
/findings comment <finding-ref> <comment>
|
|
112
116
|
Add a comment or note to a finding
|
|
113
117
|
/findings feedback <finding-ref> valid [comment]
|
|
@@ -154,7 +158,8 @@ Tips:
|
|
|
154
158
|
Lite scans require beta access plus provider-backed GitHub or GitLab sources.
|
|
155
159
|
/workspace use accepts a workspace name, prefix, or ID.
|
|
156
160
|
/findings comment, /findings feedback, and /findings fix-review use the current Apex login.
|
|
157
|
-
|
|
161
|
+
For first-class open state or a workspace that is not locally bound, use scripted
|
|
162
|
+
apex findings --workspace --company <handle> --workspace-ref <ref> --finding-state open.
|
|
158
163
|
/claim is the only onboarding command that may open a browser. Trial redemption stays scripted-only and is not exposed through MCP.
|
|
159
164
|
/claim moves trial results into the selected Cantina workspace when you can run scans there, or into your existing personal workspace.
|
|
160
165
|
Service-key commands automatically use your company when only one is available. With multiple companies, choose one interactively or pass --company.
|
package/dist/mcp.js
CHANGED
|
@@ -18,8 +18,8 @@ Suggested workflow:
|
|
|
18
18
|
3. If the user supplied a Cantina trial code, run \`apex redeem <code>\` in a terminal instead of starting device login. Never put the code in an MCP tool call.
|
|
19
19
|
4. Only when Apex is unauthenticated and no unexpired claim proof or trial code is available, call apex-auth-start and ask the user to approve the device login URL, then call apex-auth-wait with both the returned deviceCode and credentialGeneration.
|
|
20
20
|
5. Call apex-doctor for the target repository cwd.
|
|
21
|
-
6. If
|
|
22
|
-
7. Start and monitor scans with apex-scan, apex-status, apex-scans, apex-findings, and apex-export-findings.
|
|
21
|
+
6. If a repository operation needs a different local binding, call apex-workspaces and apex-workspace-use.
|
|
22
|
+
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.
|
|
23
23
|
8. For PR scans, call apex-scan with mode "pr" and one or more pullRequests.
|
|
24
24
|
9. Leave finding comments or valid/invalid feedback with apex-finding-comment and apex-finding-feedback when review collaboration is needed.
|
|
25
25
|
10. 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.
|
|
@@ -27,7 +27,7 @@ Suggested workflow:
|
|
|
27
27
|
Notes:
|
|
28
28
|
- Pass cwd explicitly for repository-specific operations.
|
|
29
29
|
- Tool calls are always non-interactive and never auto-open a browser.
|
|
30
|
-
- Finding write tools use the same Apex device-login credentials as read tools.
|
|
30
|
+
- 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.
|
|
31
31
|
- 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 an MCP prompt or tool call.
|
|
32
32
|
- HTTP-only serverless and cron integrations should use the hosted API documented at https://ai.cantina.xyz/docs/apex-api instead of launching this stdio server.
|
|
33
33
|
- 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.
|
|
@@ -52,6 +52,12 @@ function buildFlags(options) {
|
|
|
52
52
|
if (options.workspaceName) {
|
|
53
53
|
flags["workspace-name"] = options.workspaceName;
|
|
54
54
|
}
|
|
55
|
+
if (options.workspaceRef) {
|
|
56
|
+
flags["workspace-ref"] = options.workspaceRef;
|
|
57
|
+
}
|
|
58
|
+
if (options.findingState) {
|
|
59
|
+
flags["finding-state"] = options.findingState;
|
|
60
|
+
}
|
|
55
61
|
if (options.scanId) {
|
|
56
62
|
flags.scan = options.scanId;
|
|
57
63
|
}
|
|
@@ -617,21 +623,29 @@ function registerTools(server) {
|
|
|
617
623
|
}));
|
|
618
624
|
server.registerTool("apex-workspace-findings", {
|
|
619
625
|
title: "List Apex Workspace Findings",
|
|
620
|
-
description: "List findings across
|
|
626
|
+
description: "List findings across all scans in an Apex workspace with web-UI lifecycle fields and filter values. Pass company plus workspaceRef to select any accessible workspace without a local binding. Set findingState to open to exclude fixed, invalid, dismissed, and duplicate findings; open is not a status filter.",
|
|
621
627
|
inputSchema: {
|
|
622
628
|
cwd: z.string().optional(),
|
|
629
|
+
company: z.string().optional(),
|
|
630
|
+
workspaceRef: z.string().min(1).optional(),
|
|
631
|
+
findingState: z.enum(["all", "open"]).optional(),
|
|
623
632
|
filters: z.array(z.string().min(1).max(128)).max(50).optional(),
|
|
624
633
|
},
|
|
625
|
-
}, async ({ cwd, filters }) => runTool("apex-workspace-findings", async () => {
|
|
634
|
+
}, async ({ cwd, company, workspaceRef, findingState, filters }) => runTool("apex-workspace-findings", async () => {
|
|
626
635
|
const client = new ApexApiClient();
|
|
627
636
|
await requireAuthenticated(client);
|
|
628
637
|
const targetCwd = resolveCwd(cwd);
|
|
629
|
-
const payload = await commandWorkspaceFindings(client, targetCwd, buildFlags({
|
|
638
|
+
const payload = await commandWorkspaceFindings(client, targetCwd, buildFlags({
|
|
639
|
+
company,
|
|
640
|
+
workspaceRef,
|
|
641
|
+
findingState,
|
|
642
|
+
filters,
|
|
643
|
+
}));
|
|
630
644
|
return {
|
|
631
645
|
cwd: targetCwd,
|
|
632
646
|
...payload,
|
|
633
647
|
};
|
|
634
|
-
}, (value) => `Fetched Apex workspace findings for ${String(value.cwd)}.`, { cwd, filters }));
|
|
648
|
+
}, (value) => `Fetched Apex workspace findings for ${String(value.cwd)}.`, { cwd, company, workspaceRef, findingState, filters }));
|
|
635
649
|
server.registerTool("apex-finding-comment", {
|
|
636
650
|
title: "Add Apex Finding Comment",
|
|
637
651
|
description: "Add a comment or note to an Apex finding using the current Apex login.",
|
package/dist/setup.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { execFile as execFileCallback } from "node:child_process";
|
|
2
|
-
import {
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { chmod, mkdir, readFile, rename, rm, stat, writeFile, } from "node:fs/promises";
|
|
3
4
|
import os from "node:os";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import { fileURLToPath } from "node:url";
|
|
@@ -11,6 +12,7 @@ const CODEX_SKILL_NAME = "apex-cli";
|
|
|
11
12
|
const MCP_SERVER_NAME = "apex";
|
|
12
13
|
const MCP_CLIENT_ENV_NAME = "APEX_MCP_CLIENT";
|
|
13
14
|
const MCP_CLIENT_INTEGRATION_ENV_NAME = "APEX_CLIENT_INTEGRATION";
|
|
15
|
+
const PACKAGE_NAME = "@cantinasecurity/apex-cli";
|
|
14
16
|
const execFile = promisify(execFileCallback);
|
|
15
17
|
const SETUP_CLIENTS = ["codex", "claude", "copilot"];
|
|
16
18
|
function quoteShellArg(value) {
|
|
@@ -64,9 +66,9 @@ async function resolveMcpLaunchSpec(packageRoot = PACKAGE_ROOT) {
|
|
|
64
66
|
};
|
|
65
67
|
}
|
|
66
68
|
return {
|
|
67
|
-
command: "
|
|
68
|
-
args: [],
|
|
69
|
-
label:
|
|
69
|
+
command: "npx",
|
|
70
|
+
args: ["-y", "-p", `${PACKAGE_NAME}@latest`, "apex-mcp"],
|
|
71
|
+
label: `npx -y -p ${PACKAGE_NAME}@latest apex-mcp`,
|
|
70
72
|
};
|
|
71
73
|
}
|
|
72
74
|
function getCodexHome() {
|
|
@@ -188,6 +190,72 @@ async function readClaudeUserMcpConfig() {
|
|
|
188
190
|
return null;
|
|
189
191
|
}
|
|
190
192
|
}
|
|
193
|
+
async function claudeSupportsUserScope() {
|
|
194
|
+
const help = await tryExecText("claude", ["mcp", "add", "--help"]);
|
|
195
|
+
if (!help) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
return /(?:^|\s)--scope(?:\s|,|$)/m.test(`${help.stdout}\n${help.stderr}`);
|
|
199
|
+
}
|
|
200
|
+
async function writeClaudeUserMcpConfig(launch) {
|
|
201
|
+
const configPath = path.join(os.homedir(), ".claude.json");
|
|
202
|
+
let raw;
|
|
203
|
+
try {
|
|
204
|
+
raw = await readFile(configPath, "utf8");
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
if (error.code === "ENOENT") {
|
|
208
|
+
raw = null;
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
throw new Error(`Cannot update ${configPath}: the existing Claude Code config could not be read.`, { cause: error });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
let document = {};
|
|
215
|
+
if (raw) {
|
|
216
|
+
let parsed;
|
|
217
|
+
try {
|
|
218
|
+
parsed = JSON.parse(raw);
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
throw new Error(`Cannot update ${configPath}: the existing Claude Code config is not valid JSON.`);
|
|
222
|
+
}
|
|
223
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
224
|
+
throw new Error(`Cannot update ${configPath}: the existing Claude Code config must be a JSON object.`);
|
|
225
|
+
}
|
|
226
|
+
document = parsed;
|
|
227
|
+
}
|
|
228
|
+
const currentServers = document.mcpServers &&
|
|
229
|
+
typeof document.mcpServers === "object" &&
|
|
230
|
+
!Array.isArray(document.mcpServers)
|
|
231
|
+
? document.mcpServers
|
|
232
|
+
: {};
|
|
233
|
+
document.mcpServers = {
|
|
234
|
+
...currentServers,
|
|
235
|
+
[MCP_SERVER_NAME]: {
|
|
236
|
+
type: "stdio",
|
|
237
|
+
command: launch.command,
|
|
238
|
+
args: launch.args,
|
|
239
|
+
env: mcpClientEnv("claude"),
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
const configDirectory = path.dirname(configPath);
|
|
243
|
+
const temporaryPath = path.join(configDirectory, `.${path.basename(configPath)}.${process.pid}.${randomUUID()}.tmp`);
|
|
244
|
+
await mkdir(configDirectory, { recursive: true, mode: 0o700 });
|
|
245
|
+
try {
|
|
246
|
+
await writeFile(temporaryPath, `${JSON.stringify(document, null, 2)}\n`, {
|
|
247
|
+
encoding: "utf8",
|
|
248
|
+
mode: 0o600,
|
|
249
|
+
});
|
|
250
|
+
await chmod(temporaryPath, 0o600);
|
|
251
|
+
await rename(temporaryPath, configPath);
|
|
252
|
+
await chmod(configPath, 0o600);
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
await rm(temporaryPath, { force: true }).catch(() => undefined);
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
191
259
|
async function readCopilotUserMcpConfig() {
|
|
192
260
|
const configPath = path.join(getCopilotHome(), "mcp-config.json");
|
|
193
261
|
const raw = await readTextFile(configPath);
|
|
@@ -246,29 +314,37 @@ async function configureCodex(launch) {
|
|
|
246
314
|
async function configureClaude(cwd, launch) {
|
|
247
315
|
const existing = await readClaudeUserMcpConfig();
|
|
248
316
|
const env = mcpClientEnv("claude");
|
|
317
|
+
const supportsUserScope = await claudeSupportsUserScope();
|
|
249
318
|
const mcpStatus = existing === null
|
|
250
319
|
? "installed"
|
|
251
320
|
: claudeConfigMatches(existing, launch)
|
|
252
321
|
? "unchanged"
|
|
253
322
|
: "updated";
|
|
254
|
-
if (
|
|
255
|
-
|
|
323
|
+
if (supportsUserScope === false) {
|
|
324
|
+
// Older Claude Code releases reject --scope. Merge the same stdio
|
|
325
|
+
// registration into the user config while preserving all other settings.
|
|
326
|
+
await writeClaudeUserMcpConfig(launch);
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
if (existing) {
|
|
330
|
+
await execText("claude", ["mcp", "remove", "--scope", "user", MCP_SERVER_NAME]);
|
|
331
|
+
}
|
|
332
|
+
// Claude's variadic --env option consumes later tokens until `--`.
|
|
333
|
+
await execText("claude", [
|
|
334
|
+
"mcp",
|
|
335
|
+
"add",
|
|
336
|
+
"--scope",
|
|
337
|
+
"user",
|
|
338
|
+
"-e",
|
|
339
|
+
`${MCP_CLIENT_ENV_NAME}=${env[MCP_CLIENT_ENV_NAME]}`,
|
|
340
|
+
"-e",
|
|
341
|
+
`${MCP_CLIENT_INTEGRATION_ENV_NAME}=${env[MCP_CLIENT_INTEGRATION_ENV_NAME]}`,
|
|
342
|
+
"--",
|
|
343
|
+
MCP_SERVER_NAME,
|
|
344
|
+
launch.command,
|
|
345
|
+
...launch.args,
|
|
346
|
+
]);
|
|
256
347
|
}
|
|
257
|
-
// Claude's variadic --env option consumes later tokens until `--`.
|
|
258
|
-
await execText("claude", [
|
|
259
|
-
"mcp",
|
|
260
|
-
"add",
|
|
261
|
-
"--scope",
|
|
262
|
-
"user",
|
|
263
|
-
"-e",
|
|
264
|
-
`${MCP_CLIENT_ENV_NAME}=${env[MCP_CLIENT_ENV_NAME]}`,
|
|
265
|
-
"-e",
|
|
266
|
-
`${MCP_CLIENT_INTEGRATION_ENV_NAME}=${env[MCP_CLIENT_INTEGRATION_ENV_NAME]}`,
|
|
267
|
-
"--",
|
|
268
|
-
MCP_SERVER_NAME,
|
|
269
|
-
launch.command,
|
|
270
|
-
...launch.args,
|
|
271
|
-
]);
|
|
272
348
|
const skillSource = path.join(PACKAGE_ROOT, ".claude", "skills", CODEX_SKILL_NAME, "SKILL.md");
|
|
273
349
|
const skillTarget = path.join(cwd, ".claude", "skills", CODEX_SKILL_NAME, "SKILL.md");
|
|
274
350
|
const skillContent = await readFile(skillSource, "utf8");
|
|
@@ -279,7 +355,9 @@ async function configureClaude(cwd, launch) {
|
|
|
279
355
|
kind: "mcp",
|
|
280
356
|
status: mcpStatus,
|
|
281
357
|
path: path.join(os.homedir(), ".claude.json"),
|
|
282
|
-
detail:
|
|
358
|
+
detail: supportsUserScope === false
|
|
359
|
+
? `Registered ${MCP_SERVER_NAME} -> ${launch.label} in Claude Code user config (legacy CLI compatibility)`
|
|
360
|
+
: `Registered ${MCP_SERVER_NAME} -> ${launch.label} in Claude Code user config`,
|
|
283
361
|
},
|
|
284
362
|
{
|
|
285
363
|
client: "claude",
|
package/dist/telemetry.js
CHANGED
|
@@ -19,6 +19,7 @@ const KNOWN_CLI_FLAG_NAMES = [
|
|
|
19
19
|
"force",
|
|
20
20
|
"format",
|
|
21
21
|
"filter",
|
|
22
|
+
"finding-state",
|
|
22
23
|
"help",
|
|
23
24
|
"json",
|
|
24
25
|
"label",
|
|
@@ -40,6 +41,7 @@ const KNOWN_CLI_FLAG_NAMES = [
|
|
|
40
41
|
"status",
|
|
41
42
|
"suggested-severity",
|
|
42
43
|
"workspace-name",
|
|
44
|
+
"workspace-ref",
|
|
43
45
|
"workspace",
|
|
44
46
|
];
|
|
45
47
|
const KNOWN_CLI_COMMANDS = [
|
|
@@ -230,6 +232,7 @@ function sanitizeCliFlags(flags) {
|
|
|
230
232
|
workspaceFindings: boolFlag(flags, "workspace"),
|
|
231
233
|
companyProvided: stringFlag(flags, "company") !== null,
|
|
232
234
|
workspaceNameProvided: stringFlag(flags, "workspace-name") !== null,
|
|
235
|
+
workspaceRefProvided: stringFlag(flags, "workspace-ref") !== null,
|
|
233
236
|
nameProvided: stringFlag(flags, "name") !== null,
|
|
234
237
|
expiresAtProvided: stringFlag(flags, "expires-at") !== null,
|
|
235
238
|
scanIdProvided: stringFlag(flags, "scan") !== null,
|
|
@@ -247,6 +250,7 @@ function sanitizeCliFlags(flags) {
|
|
|
247
250
|
sourceMode: safeEnum(stringFlag(flags, "source-mode"), ["auto", "remote", "local"]),
|
|
248
251
|
format: safeEnum(stringFlag(flags, "format"), ["markdown", "json", "gitlab-sast"]),
|
|
249
252
|
feedbackStatus: safeEnum(stringFlag(flags, "status"), ["valid", "invalid"]),
|
|
253
|
+
findingState: safeEnum(stringFlag(flags, "finding-state"), ["all", "open"]),
|
|
250
254
|
fixReviewTarget: safeFixReviewTarget(stringFlag(flags, "review-target")),
|
|
251
255
|
dismissalReason: safeEnum(stringFlag(flags, "dismissal-reason"), [
|
|
252
256
|
"false-positive",
|
|
@@ -408,6 +412,7 @@ export function sanitizeMcpInput(input = {}) {
|
|
|
408
412
|
companyProvided: typeof input.company === "string" && input.company.trim().length > 0,
|
|
409
413
|
workspaceNameProvided: typeof input.workspaceName === "string" && input.workspaceName.trim().length > 0,
|
|
410
414
|
workspaceRefProvided: typeof input.workspaceRef === "string" && input.workspaceRef.trim().length > 0,
|
|
415
|
+
findingState: safeEnum(typeof input.findingState === "string" ? input.findingState : null, ["all", "open"]),
|
|
411
416
|
scanIdProvided: typeof input.scanId === "string" && input.scanId.trim().length > 0,
|
|
412
417
|
findingRefProvided: typeof input.findingRef === "string" && input.findingRef.trim().length > 0,
|
|
413
418
|
outputProvided: typeof input.output === "string" && input.output.trim().length > 0,
|
package/package.json
CHANGED
package/skills/apex-cli/SKILL.md
CHANGED
|
@@ -37,7 +37,7 @@ Workflow:
|
|
|
37
37
|
4. For repository work, pass `cwd` explicitly.
|
|
38
38
|
5. Call `apex-doctor` before starting a scan if workspace binding or source planning might be unclear.
|
|
39
39
|
6. If the directory is not bound to the right workspace, call `apex-workspaces` and `apex-workspace-use`.
|
|
40
|
-
7. Use `apex-scan`, `apex-status`, `apex-scans`, `apex-findings`, and `apex-export-findings` for the scan lifecycle. Use `apex-workspace-findings` for the cross-scan workspace browser view.
|
|
40
|
+
7. Use `apex-scan`, `apex-status`, `apex-scans`, `apex-findings`, and `apex-export-findings` for the scan lifecycle. Use `apex-workspace-findings` for the cross-scan workspace browser view. To review unresolved findings across any accessible workspace, first call `apex-workspaces` with the company, then call `apex-workspace-findings` with `company`, `workspaceRef`, and `findingState: "open"`; this does not change the local binding.
|
|
41
41
|
8. For Lite scans, call `apex-scan` with `mode: "lite"` only when the company has Lite Scan beta access and the sources are provider-backed.
|
|
42
42
|
9. For PR scans, call `apex-scan` with `mode: "pr"` and `pullRequests`.
|
|
43
43
|
10. Use `apex-finding-comment` and `apex-finding-feedback` when the user wants to leave review notes or valid/invalid feedback on a finding.
|
|
@@ -74,8 +74,9 @@ Guidelines:
|
|
|
74
74
|
- Use `force: true` on `apex-scan` only when the user explicitly wants to replace or overlap an active scan.
|
|
75
75
|
- When checking a scan that is not the workspace binding's latest scan, pass `scanId` to `apex-status`; use `apex-scans` first if you need to discover scan IDs.
|
|
76
76
|
- Prefer `apex-findings` for quick inspection and `apex-export-findings` when the user needs a file artifact.
|
|
77
|
-
- Use `apex-workspace-findings` when the user asks for findings across a workspace instead of one scan.
|
|
78
|
-
-
|
|
77
|
+
- Use `apex-workspace-findings` when the user asks for findings across a workspace instead of one scan. Set `findingState: "open"` for findings needing review; open excludes fixed, invalid, dismissed, and duplicate findings and is not `status:open`. Pass `company` plus a workspace name, prefix, or ID as `workspaceRef` to avoid relying on or mutating a local binding.
|
|
78
|
+
- Workspace finding responses expose the web UI lifecycle fields `status`, `validity`, `reviewState`, `isDuplicate`, `duplicateOfId`, `repository`, and `sourceFile`. Supported filter facets are `severity`, `scan`, `repo`, `review`, `impact`, `likelihood`, `status`, `validation`, `visibility`, `duplicate`, and `source`; a leading `!` negates a filter.
|
|
79
|
+
- Finding comments, feedback, and fix review scan starts use the same Apex device-login credentials as read tools. If a write tool reports missing auth, re-run `apex-auth-status`, check `apex-trial-status`, and complete `apex-auth-start` / `apex-auth-wait` only when no unexpired local claim proof is available. Never ask for browser cookies or `CANTINA_AUTH_TOKEN`; an error requiring that variable proves the MCP server is outdated, so update Apex CLI, re-run `apex setup <client>`, restart the client, and retry.
|
|
79
80
|
- Anonymous telemetry records only sanitized command/tool metadata such as command names, enum modes, counts, durations, success/failure categories, CLI version, and client integration. It must not include raw repository paths, scan IDs, finding IDs, comments, file paths, PR URLs, trial codes, claim tokens, onboarding fingerprints, or bearer tokens.
|
|
80
81
|
- Invalid finding feedback requires `dismissalReason`; valid feedback can include `suggestedSeverity`, including `extreme`.
|
|
81
82
|
- PR-based fix reviews can pass `fixPrUrls` directly to `apex-finding-fix-review`; agents that also need to record fixed feedback can first call `apex-finding-feedback` with `status: "valid"`, `labels: ["fixed"]`, and `fixPrUrls`.
|