@cantinasecurity/apex-cli 0.1.27 → 0.1.33
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 +25 -5
- 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 +90 -19
- package/dist/apex.js +41 -8
- package/dist/args.js +17 -1
- package/dist/auth.js +2 -2
- package/dist/commands.js +64 -11
- package/dist/findings.js +15 -3
- package/dist/help.js +51 -4
- package/dist/mcp.js +94 -12
- package/dist/scan.js +49 -7
- package/dist/service-key-environment.js +3 -3
- package/dist/service-keys.js +2 -1
- package/dist/shell.js +29 -6
- package/dist/telemetry.js +4 -2
- package/dist/threat-model.js +435 -0
- package/package.json +1 -1
- package/skills/apex-cli/SKILL.md +28 -5
|
@@ -11,9 +11,11 @@ This skill is bundled with Apex CLI and can be installed into the current reposi
|
|
|
11
11
|
|
|
12
12
|
## Instructions
|
|
13
13
|
|
|
14
|
-
Use Apex through the MCP server when the `apex-*` tools are available.
|
|
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
|
|
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-status`, `apex-cancel-scan`, `apex-findings`, `apex-workspace-findings`, `apex-finding-tickets`, `apex-finding-link-ticket`, and `apex-finding-send`.
|
|
17
|
+
|
|
18
|
+
For serverless, cron, or other explicit HTTP/JSON integrations, use the hosted Apex REST API instead of CLI or local stdio MCP. The hosted MCP and REST contract is documented at https://ai.cantina.xyz/docs/apex-api.
|
|
17
19
|
|
|
18
20
|
For on-prem or other non-default Apex hosts, make sure the CLI or MCP server runs with `APEX_BASE_URL` set to the external HTTPS Bedrock origin before auth or provider connection flows.
|
|
19
21
|
|
|
@@ -55,11 +57,12 @@ If the Apex MCP server is not configured, fall back to the local CLI:
|
|
|
55
57
|
- `apex-trial-status` is MCP-only; do not invoke it as a CLI command. In a CLI-only flow, use the `claimDeadline` returned by `apex redeem <code> --json` without repeating or persisting the secret code.
|
|
56
58
|
- After a guest scan, tell the user to run `apex claim` on the same machine before that returned claim deadline. If the deadline is unavailable or uncertain, do not guess or start a replacement login; run `apex claim` and let the server validate the proof.
|
|
57
59
|
- Claim moves the guest workspace, scans, and findings into the currently selected Cantina workspace when the account can run scans there. Personal workspace owners receive the trial in their existing workspace instead of a duplicate. If the selected workspace cannot run scans, tell the user to switch to one that can and retry before the claim deadline.
|
|
58
|
-
- Service-key
|
|
60
|
+
- Service-key creation, listing, and revocation have no MCP tools. Never ask a user to paste a service key into a prompt, and never place one in a tool argument, tool result, source file, or log. Hosted MCP clients may read it from a secret-backed environment variable for the HTTP Authorization header.
|
|
59
61
|
- If a user explicitly needs a new service key, tell them to run `apex login` if needed, then run `apex service-key create --name <name> --scope <scope[,scope]>` directly in their own terminal. Use only the required scopes: `scans:create` to query credit availability or trigger, `scans:read` to poll status, and `findings:read` to retrieve findings. A P1-only adapter can use `--scope scans:read,findings:read`; omitting `--scope` grants all three. The CLI automatically selects the company when only one is available and prompts when there are several; use `--company <id-or-handle>` to skip that prompt or resolve a non-interactive command. Service-key commands never initiate login, and the secret is shown once.
|
|
60
|
-
- For a
|
|
62
|
+
- For a hosted MCP client, connect to `https://ai.cantina.xyz/mcp` and send `APEX_SERVICE_KEY` as a Bearer header. The service key authenticates hosted MCP, but not the local CLI or stdio MCP; those use device login.
|
|
63
|
+
- For a headless REST adapter, read the service key from the secret-backed `APEX_SERVICE_KEY` environment variable, send it as a bearer token to `/api/apex/v1`, and redact it from errors and logs. Do not shell out to `apex` or launch local MCP from the adapter.
|
|
61
64
|
- Hosted API integrations can call `GET /api/apex/v1/credits` with `scans:create` scope to inspect the organization's current standard, audit, and Lite balances and effective scan-type availability. Treat the response as a point-in-time snapshot; scan creation remains authoritative.
|
|
62
|
-
- Hosted
|
|
65
|
+
- Hosted remote-repository scans require the GitHub repository to be connected to the service key's Apex organization. They reuse a linked active workspace or create and link one when no match exists; use optional `workspaceId` to disambiguate multiple matches and treat explicit workspace/repository mismatches as HTTP `409` setup errors. Local or non-Git REST scans use the documented source-upload flow; send `generateThreatModel: true` for the first scan of a newly created local workspace.
|
|
63
66
|
- Hosted scan triggers require `Idempotency-Key`. Reuse one stable key only for an identical transport/timeout/5xx retry. After correcting a definitive 4xx response, or for any new intended scan, use a new key.
|
|
64
67
|
- Use `--non-interactive` and `--no-open` for automation-friendly CLI calls.
|
|
65
68
|
- Use `--json` whenever structured output is helpful.
|
|
@@ -75,6 +78,7 @@ If the Apex MCP server is not configured, fall back to the local CLI:
|
|
|
75
78
|
- `apex-workspace-use` accepts a workspace name, prefix, or ID.
|
|
76
79
|
- Use `sourceMode: "remote"` only when the user explicitly wants to forbid local snapshot fallbacks.
|
|
77
80
|
- 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.
|
|
81
|
+
- Use only the Bedrock `scanId` returned by Apex. Kernel execution IDs are private and are not returned or accepted as aliases by CLI or MCP tools.
|
|
78
82
|
- 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`.
|
|
79
83
|
- 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.
|
|
80
84
|
- 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.
|
|
@@ -86,6 +90,7 @@ If the Apex MCP server is not configured, fall back to the local CLI:
|
|
|
86
90
|
|
|
87
91
|
## Examples
|
|
88
92
|
|
|
93
|
+
- Connect a production MCP client: configure `https://ai.cantina.xyz/mcp` as a Streamable HTTP server and source its Bearer token from `APEX_SERVICE_KEY`, then verify `apex-credits` without printing the credential.
|
|
89
94
|
- Redeem a supplied trial code: run `apex redeem <code> --json` in a terminal, continue with MCP only after a `redeemed` or `idempotent` outcome, then check `apex-trial-status` and remind the user about `apex claim` after findings only while `claimAvailable` remains true.
|
|
90
95
|
- Start a scan for the current repository or directory: run `apex-doctor`, bind a workspace if needed, then call `apex-scan`.
|
|
91
96
|
- Start a Lite scan: call `apex-scan` with `mode: "lite"` after confirming beta access and provider-backed sources.
|
|
@@ -95,3 +100,18 @@ If the Apex MCP server is not configured, fall back to the local CLI:
|
|
|
95
100
|
- Review all open workspace findings: call `apex-workspaces` with the company, then call `apex-workspace-findings` with the selected `workspaceRef` and `findingState: "open"`.
|
|
96
101
|
- Leave review feedback: call `apex-finding-comment` to add a note, or `apex-finding-feedback` with `status` set to `valid` or `invalid`.
|
|
97
102
|
- Run a Fix PR review: call `apex-finding-fix-review` with `fixPrUrls`, optionally after recording fixed feedback with `apex-finding-feedback`.
|
|
103
|
+
|
|
104
|
+
## Send findings to Linear and Jira
|
|
105
|
+
|
|
106
|
+
- Use `apex-finding-send` to create a ticket from a finding, using the connected integration and saved frontend routing. Local input: `findingRef`, `provider: "linear" | "jira"`, optional `cwd`/`scanId`. Hosted input: `findingId` (UUID) and `provider`, with explicit `findings:write` scope.
|
|
107
|
+
- CLI fallback: `apex findings send <finding-ref> <linear|jira> --json`. This uses device login and stays non-interactive in automation.
|
|
108
|
+
- Connect the integration and configure its destination in Apex first: Linear workspace project/labels or company default; Jira workspace site/project/issue type. Missing destinations return a typed error; do not guess routing or create a different integration.
|
|
109
|
+
- The result has `created` and `export` with issue key/URL. An existing sent or linked ticket returns `created: false`; an active export is a conflict. After ambiguous transport/provider failure, inspect `apex-finding-tickets` and provider state before retrying. Do not automatically repeat ticket creation.
|
|
110
|
+
- Ticket content uses the same title, severity, finding details, and Apex link as the frontend.
|
|
111
|
+
|
|
112
|
+
## Existing Linear and Jira tickets
|
|
113
|
+
|
|
114
|
+
- After the company connects its integration, use `apex-finding-link-ticket` with `findingRef`, `provider: "linear" | "jira"`, and `issueRef` (ticket key or HTTPS issue URL). Local MCP accepts optional `cwd` and `scanId` and uses device login. A finding UUID works without a local binding; an identifier requires the bound workspace.
|
|
115
|
+
- Use `apex-finding-tickets` to read existing links. CLI fallback: `apex findings link-ticket <finding-ref> <linear|jira> <issue-key|url> --json`, then `apex findings tickets <finding-ref> --json`.
|
|
116
|
+
- Hosted MCP uses `findingId` (UUID) instead of `findingRef`, with no `cwd` or `scanId`. Linking requires explicit `findings:write`; listing requires `findings:read`. Default service-key scopes remain scans:create, scans:read, findings:read. Do not infer write permission from read access.
|
|
117
|
+
- Apex verifies the ticket through the connected integration and records the frontend link; it does not create or modify a provider ticket. Repeating the same association is safe. A different existing link or in-progress export is a conflict: do not replace it or create another ticket as a fallback. For ambiguous Jira sites, use the full issue URL.
|
|
@@ -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.33"
|
|
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.33"
|
|
18
18
|
},
|
|
19
19
|
"description": "Run Apex security scans and review findings from Claude Code.",
|
|
20
|
-
"version": "0.1.
|
|
20
|
+
"version": "0.1.33",
|
|
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.33 apex-mcp
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
That keeps marketplace installs independent of a user's global `apex` install.
|
package/README.md
CHANGED
|
@@ -152,30 +152,30 @@ unclaimed local trial proof so `apex claim` can still use it.
|
|
|
152
152
|
Agent-safe copy/paste prompt (substitute the issued code):
|
|
153
153
|
|
|
154
154
|
```text
|
|
155
|
-
Install and run Cantina Apex CLI 0.1.
|
|
155
|
+
Install and run Cantina Apex CLI 0.1.33 to start one free standard security scan of this repository immediately.
|
|
156
156
|
|
|
157
157
|
Use this trial code only where <TRIAL_CODE> appears below: SPBT-DEVREL-XXXXXXXX
|
|
158
158
|
|
|
159
159
|
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`.
|
|
160
160
|
|
|
161
161
|
Use this exact command prefix for every Apex command:
|
|
162
|
-
npx -y -p @cantinasecurity/apex-cli@0.1.
|
|
162
|
+
npx -y -p @cantinasecurity/apex-cli@0.1.33 apex
|
|
163
163
|
|
|
164
164
|
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.
|
|
165
165
|
2. Redeem, with at most one response-loss recovery retry:
|
|
166
|
-
npx -y -p @cantinasecurity/apex-cli@0.1.
|
|
166
|
+
npx -y -p @cantinasecurity/apex-cli@0.1.33 apex redeem <TRIAL_CODE> --json
|
|
167
167
|
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.
|
|
168
168
|
3. Immediately start exactly one scan; do not wait for another instruction:
|
|
169
|
-
npx -y -p @cantinasecurity/apex-cli@0.1.
|
|
169
|
+
npx -y -p @cantinasecurity/apex-cli@0.1.33 apex scan --mode standard --non-interactive --no-open --json
|
|
170
170
|
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.
|
|
171
171
|
4. About every 60 seconds, poll that exact scan:
|
|
172
|
-
npx -y -p @cantinasecurity/apex-cli@0.1.
|
|
172
|
+
npx -y -p @cantinasecurity/apex-cli@0.1.33 apex status --scan <SCAN_ID> --json
|
|
173
173
|
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.
|
|
174
174
|
5. Fetch that exact scan's findings:
|
|
175
|
-
npx -y -p @cantinasecurity/apex-cli@0.1.
|
|
175
|
+
npx -y -p @cantinasecurity/apex-cli@0.1.33 apex findings --scan <SCAN_ID> --json
|
|
176
176
|
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.
|
|
177
177
|
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:
|
|
178
|
-
npx -y -p @cantinasecurity/apex-cli@0.1.
|
|
178
|
+
npx -y -p @cantinasecurity/apex-cli@0.1.33 apex claim
|
|
179
179
|
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.
|
|
180
180
|
|
|
181
181
|
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.
|
|
@@ -212,6 +212,9 @@ Supported shell commands:
|
|
|
212
212
|
- `/scans`
|
|
213
213
|
- `/findings [scan-id]`
|
|
214
214
|
- `/findings workspace [filter...]`
|
|
215
|
+
- `/findings tickets <finding-ref>`
|
|
216
|
+
- `/findings send <finding-ref> <linear|jira>`
|
|
217
|
+
- `/findings link-ticket <finding-ref> <linear|jira> <issue-key|url>`
|
|
215
218
|
- `/findings comment <finding-id|finding-identifier> <comment>`
|
|
216
219
|
- `/findings feedback <finding-id|finding-identifier> valid [comment]`
|
|
217
220
|
- `/findings feedback <finding-id|finding-identifier> invalid <false-positive|by-design|not-relevant> [comment]`
|
|
@@ -247,6 +250,9 @@ Supported shell commands:
|
|
|
247
250
|
- `apex scans`
|
|
248
251
|
- `apex findings [--scan <scan-id>]`
|
|
249
252
|
- `apex findings --workspace [--company <handle>] [--workspace-ref <name|prefix|id>] [--finding-state all|open] [--filter <[!]facet:value>]`
|
|
253
|
+
- `apex findings tickets <finding-ref> [--scan <scan-id>] [--json]`
|
|
254
|
+
- `apex findings send <finding-ref> <linear|jira> [--scan <scan-id>] [--json]`
|
|
255
|
+
- `apex findings link-ticket <finding-ref> <linear|jira> <issue-key|url> [--scan <scan-id>] [--json]`
|
|
250
256
|
- `apex findings comment <finding-id|finding-identifier> --content <markdown> [--parent-comment <comment-id>] [--scan <scan-id>]`
|
|
251
257
|
- `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>]`
|
|
252
258
|
- `apex findings fix-review <finding-id|finding-identifier> [--scan <scan-id>] [--review-target pull-request|repository-current] [--fix-pr-url <github-pr-url>]`
|
|
@@ -279,9 +285,9 @@ Helpful workspace flags:
|
|
|
279
285
|
|
|
280
286
|
`apex credits` shows standard, audit, fix review, and Lite scan entitlements when the server returns them.
|
|
281
287
|
|
|
282
|
-
## Service Keys And The
|
|
288
|
+
## Service Keys, Hosted MCP, And The REST API
|
|
283
289
|
|
|
284
|
-
Use the
|
|
290
|
+
Use hosted MCP for production agents that need a normal HTTPS endpoint. Use the REST API for explicit HTTP/JSON integrations such as serverless functions, cron jobs, and internal audit hubs. Use the CLI or local stdio MCP server when an agent needs direct access to a repository checkout or local archive. The complete hosted integration contract is in the [Apex API documentation](https://ai.cantina.xyz/docs/apex-api).
|
|
285
291
|
|
|
286
292
|
An organization manager can create a service key with an existing Apex device-login session. Service-key commands never start device login implicitly, so run `apex login` first if needed:
|
|
287
293
|
|
|
@@ -296,7 +302,7 @@ The CLI automatically uses your company when only one is available. If you belon
|
|
|
296
302
|
|
|
297
303
|
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.
|
|
298
304
|
|
|
299
|
-
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
|
|
305
|
+
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. Ticket linking additionally requires 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`.
|
|
300
306
|
|
|
301
307
|
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.
|
|
302
308
|
|
|
@@ -309,7 +315,40 @@ apex service-key revoke <key-id>
|
|
|
309
315
|
|
|
310
316
|
Service-key management is intentionally available only through the scripted CLI. It is not exposed as an MCP tool or interactive-shell command, which keeps raw secrets out of model tool results and transcripts. The CLI requires an existing device-login bearer session, never initiates login from these commands, and never stores a created service key locally.
|
|
311
317
|
|
|
312
|
-
`APEX_SERVICE_KEY` authenticates
|
|
318
|
+
`APEX_SERVICE_KEY` authenticates hosted MCP and REST requests; it does not authenticate the local CLI or stdio MCP and does not replace `apex login` there. When `apex doctor` finds a service key but no active local device-login session, it reports that split before starting a login flow. `APEX_API_KEY` is also recognized for this diagnostic so older integrations receive migration guidance, but `APEX_SERVICE_KEY` is the documented hosted-integration variable.
|
|
319
|
+
|
|
320
|
+
### Hosted HTTPS MCP
|
|
321
|
+
|
|
322
|
+
Point any Streamable HTTP MCP client at `https://ai.cantina.xyz/mcp` and send the service key as `Authorization: Bearer <TOKEN>`. No local `apex-mcp` process, browser, TTY, or device-login session is required.
|
|
323
|
+
|
|
324
|
+
Keep the key in the environment. For Codex:
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
export APEX_SERVICE_KEY='<service-key>'
|
|
328
|
+
codex mcp add apex \
|
|
329
|
+
--url https://ai.cantina.xyz/mcp \
|
|
330
|
+
--bearer-token-env-var APEX_SERVICE_KEY
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Claude Code can expand the same environment variable from `.mcp.json`:
|
|
334
|
+
|
|
335
|
+
```json
|
|
336
|
+
{
|
|
337
|
+
"mcpServers": {
|
|
338
|
+
"apex": {
|
|
339
|
+
"type": "http",
|
|
340
|
+
"url": "https://ai.cantina.xyz/mcp",
|
|
341
|
+
"headers": {
|
|
342
|
+
"Authorization": "Bearer ${APEX_SERVICE_KEY}"
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
The hosted server exposes `apex-credits`, `apex-workspaces`, `apex-scan`, `apex-status`, `apex-cancel-scan`, `apex-findings`, `apex-workspace-findings`, `apex-finding-tickets`, `apex-finding-link-ticket`, and `apex-finding-send`. Ticket creation and linking require an explicit `findings:write` scope and a connected Linear/Jira integration. It operates only on repositories and workspaces available to the service key's organization. Use local stdio MCP for device auth, local filesystem/archive access, provider connection, setup, comments, feedback, fix reviews, and trial flows.
|
|
350
|
+
|
|
351
|
+
### Hosted REST API
|
|
313
352
|
|
|
314
353
|
Use the service key as a bearer token with the hosted scan lifecycle:
|
|
315
354
|
|
|
@@ -337,7 +376,7 @@ curl \
|
|
|
337
376
|
https://ai.cantina.xyz/api/apex/v1/scans/$SCAN_ID/findings
|
|
338
377
|
```
|
|
339
378
|
|
|
340
|
-
For a remote GitHub scan, the repository must
|
|
379
|
+
For a remote GitHub scan, the repository must be connected to the service key's Apex organization. Apex reuses an active linked workspace when one exists; if none exists, the request creates and links a workspace named after the canonical repository before the scan starts. Pass `workspaceId` to disambiguate multiple matches. An explicit workspace/repository mismatch returns HTTP `409` instead of silently rewriting that workspace.
|
|
341
380
|
|
|
342
381
|
For a local or non-Git checkout, use the service API's source-upload flow instead. It can create or reuse a workspace, uploads a `tar.gz` archive, and starts the scan with a `local_archive` source. A newly created local workspace has no threat model to reuse, so its first scan must send `generateThreatModel: true`. See the [Apex service API guide](https://ai.cantina.xyz/docs/apex-api) for the current request schemas and limits.
|
|
343
382
|
|
|
@@ -347,11 +386,11 @@ Every trigger request requires an `Idempotency-Key`. Generate one stable key for
|
|
|
347
386
|
|
|
348
387
|
Example prompt for Claude Code:
|
|
349
388
|
|
|
350
|
-
> Build an Apex adapter for our serverless audit hub using the hosted
|
|
389
|
+
> Build an Apex adapter for our serverless audit hub using the hosted REST API, not the Apex CLI or local stdio MCP server. Read `APEX_SERVICE_KEY` from the environment, generate one stable `Idempotency-Key` per intended scan, and reuse it with the identical request body only after a transport error, timeout, or `5xx`; after correcting a definitive `4xx`, use a new key. POST the connected GitHub repository URL and exact commit to `/api/apex/v1/scans`, include `workspaceId` when needed to disambiguate matches, and handle a `409` workspace-resolution response. Treat optional `paths` as non-binding focus hints, not a security boundary. Poll `/api/apex/v1/scans/{scanId}` until terminal, then fetch `/api/apex/v1/scans/{scanId}/findings`. Preserve the raw GitLab SAST payload, including drafts, and deduplicate findings by stable ID. Never print or log the service key.
|
|
351
390
|
|
|
352
391
|
Example prompt for Codex:
|
|
353
392
|
|
|
354
|
-
> Add Apex to this headless auditor integration using the hosted REST API. Use bearer auth from `APEX_SERVICE_KEY`; do not shell out to `apex` or start
|
|
393
|
+
> Add Apex to this headless auditor integration using the hosted REST API. Use bearer auth from `APEX_SERVICE_KEY`; do not shell out to `apex` or start a local stdio MCP process. Implement scan trigger, `409` workspace-resolution handling, status polling with bounded backoff, terminal failure handling, and findings retrieval. Send the connected GitHub `repoUrl`, exact `commit`, optional `paths`, and optional `workspaceId`; treat `paths` as non-binding focus hints rather than a hard security boundary. Generate a stable `Idempotency-Key` for each intended scan. Preserve it only for an identical transport/timeout/`5xx` retry; after correcting a definitive `4xx`, submit with a new key. Keep the returned `scanId`, and store the unmodified findings payload, including drafts, alongside normalized findings. Add tests with mocked HTTP responses and ensure credentials are redacted from errors and logs.
|
|
355
394
|
|
|
356
395
|
For a P1-only integration where scans are started in the Apex UI:
|
|
357
396
|
|
|
@@ -390,6 +429,29 @@ The matching MCP flow is `apex-finding-fix-review` with optional `fixPrUrls`. Ag
|
|
|
390
429
|
|
|
391
430
|
The matching CLI and MCP flows intentionally use the same device-login session so agents should not ask users to paste browser cookies or auth tokens.
|
|
392
431
|
|
|
432
|
+
Send a finding to Linear or Jira to create a ticket, just as in the frontend:
|
|
433
|
+
|
|
434
|
+
```bash
|
|
435
|
+
apex findings send KERN2-25 linear --json
|
|
436
|
+
apex findings send KERN2-25 jira --json
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
Connect the integration and configure its destination in Apex first. Sending uses the same finding title, details, severity, Apex link, and saved routing as the frontend: workspace Linear project/labels (or the company default), or the workspace Jira site/project/issue type. The response contains `created` and `export` with the ticket key and URL. Already sent or linked findings return their existing ticket with `created: false`; active exports return a conflict. If a send times out, inspect `apex findings tickets` and the provider before retrying; the CLI never automatically repeats an uncertain creation.
|
|
440
|
+
|
|
441
|
+
Local MCP exposes `apex-finding-send` with `findingRef`, `provider`, and optional `cwd`/`scanId`. Hosted MCP uses `findingId` (UUID) and `provider`, with explicit `findings:write`. REST clients call `POST /api/apex/v1/findings/{findingId}/issue-trackers/{provider}` with no body. Provider is `linear` or `jira`. These paths all reuse the frontend's ticket-creation service.
|
|
442
|
+
|
|
443
|
+
Link an existing ticket after connecting Linear or Jira in company settings:
|
|
444
|
+
|
|
445
|
+
```bash
|
|
446
|
+
apex findings link-ticket KERN2-25 linear SEC-123 --json
|
|
447
|
+
apex findings link-ticket KERN2-25 jira https://acme.atlassian.net/browse/SEC-123 --json
|
|
448
|
+
apex findings tickets KERN2-25 --json
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
Use a finding UUID directly, or resolve its identifier/URL from the bound workspace and optional `--scan`. Ticket keys and HTTPS issue URLs are accepted; Linear also accepts an issue UUID. When Jira has several accessible sites, use a full issue URL or configure the workspace destination. Apex verifies the ticket through the company's existing integration and uses the same link shown in the frontend. One ticket per provider can be linked to a finding. Repeating the same link is safe; a different existing link or an in-progress export returns a conflict. This operation does not create or modify provider tickets.
|
|
452
|
+
|
|
453
|
+
Local MCP exposes `apex-finding-link-ticket` with `findingRef`, `provider`, `issueRef`, and optional `cwd`/`scanId`; `apex-finding-tickets` lists links. Both use device login. Hosted MCP exposes the same tool names with `findingId` (the finding UUID), without local filesystem arguments. Hosted writes require the explicit `findings:write` scope; reads require `findings:read`. Existing keys and omitted-scope defaults retain the original three scopes. Create a dedicated key in your own terminal with `apex service-key create --name "Ticket linking" --scope findings:read,findings:write` and keep the secret in your secret manager. REST clients use `GET`/`POST /api/apex/v1/findings/{findingId}/tickets`; POST accepts `{"provider":"linear","issueRef":"SEC-123"}`.
|
|
454
|
+
|
|
393
455
|
## Local Source Scans
|
|
394
456
|
|
|
395
457
|
`apex scan` now works against any local source root you point it at. By default, that source root is the current working directory:
|
|
@@ -414,9 +476,16 @@ Lite scans use `lite` as the scan mode and require the Lite Scan beta feature pl
|
|
|
414
476
|
|
|
415
477
|
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.
|
|
416
478
|
|
|
479
|
+
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.
|
|
480
|
+
|
|
417
481
|
## LLM / MCP Usage
|
|
418
482
|
|
|
419
|
-
|
|
483
|
+
Apex has two MCP transports:
|
|
484
|
+
|
|
485
|
+
- Hosted Streamable HTTP at `https://ai.cantina.xyz/mcp`, authenticated with an organization service key, for production agents that do not need the caller's filesystem.
|
|
486
|
+
- The `apex-mcp` stdio server shipped by this package, authenticated with device login, for repository-local and interactive agent workflows.
|
|
487
|
+
|
|
488
|
+
See [Hosted HTTPS MCP](#hosted-https-mcp) for the production URL and Bearer-token configuration. The rest of this section configures the local stdio server.
|
|
420
489
|
|
|
421
490
|
If Apex is installed globally, prefer:
|
|
422
491
|
|
|
@@ -511,7 +580,7 @@ If you need to launch through `pnpm`, use `--silent`:
|
|
|
511
580
|
|
|
512
581
|
Do not point an MCP client at plain `pnpm mcp`. `pnpm` writes its script banner to `stdout` before the protocol stream, which can break the `initialize` handshake.
|
|
513
582
|
|
|
514
|
-
The MCP server exposes Apex-specific tools for:
|
|
583
|
+
The local stdio MCP server exposes Apex-specific tools for:
|
|
515
584
|
|
|
516
585
|
- auth status and device login
|
|
517
586
|
- local guest-session deadlines through `apex-trial-status` (never the code or claim token)
|
|
@@ -522,9 +591,11 @@ The MCP server exposes Apex-specific tools for:
|
|
|
522
591
|
- Lite scan start by calling `apex-scan` with `mode: "lite"`
|
|
523
592
|
- PR scan start by calling `apex-scan` with `mode: "pr"` and `pullRequests`
|
|
524
593
|
- finding comments and valid/invalid feedback with `apex-finding-comment` and `apex-finding-feedback`
|
|
594
|
+
- Create Linear/Jira tickets with `apex-finding-send` using the saved frontend destination
|
|
595
|
+
- Link existing Linear/Jira tickets with `apex-finding-link-ticket` and inspect links with `apex-finding-tickets`
|
|
525
596
|
- Fix PR and current-repository fix review scans with `apex-finding-fix-review`, optionally after `apex-finding-feedback`
|
|
526
597
|
|
|
527
|
-
|
|
598
|
+
Neither MCP surface exposes trial redemption, trial claim, or service-key creation, listing, or revocation as tools. When a user supplies a Cantina trial code, run `apex redeem <code>` in a terminal instead of starting device login or putting the code in an MCP call. Production MCP clients should connect to `https://ai.cantina.xyz/mcp`; HTTP applications can use the [hosted Apex REST API](https://ai.cantina.xyz/docs/apex-api) directly. Do not launch the local stdio server as an HTTP adapter.
|
|
528
599
|
|
|
529
600
|
For repository-scoped operations, pass `cwd` explicitly so the server can resolve the right `.apex/workspace.json` binding and repository roots.
|
|
530
601
|
|
|
@@ -586,7 +657,7 @@ Telemetry event posts do not include bearer tokens. The telemetry endpoint is `P
|
|
|
586
657
|
{
|
|
587
658
|
"schemaVersion": 1,
|
|
588
659
|
"event": "apex.invocation.completed",
|
|
589
|
-
"cliVersion": "0.1.
|
|
660
|
+
"cliVersion": "0.1.33",
|
|
590
661
|
"invocation": {
|
|
591
662
|
"surface": "mcp",
|
|
592
663
|
"command": "apex-scan",
|
|
@@ -617,7 +688,7 @@ The npm package also includes marketplace-ready plugin artifacts:
|
|
|
617
688
|
- `.claude-plugin/plugin.json` and `.mcp.claude.json` for Claude Code plugin installs
|
|
618
689
|
- `.claude-plugin/marketplace.json` for a Claude marketplace entry backed by the public npm package
|
|
619
690
|
|
|
620
|
-
These plugin installs launch the pinned npm package with `npx -y -p @cantinasecurity/apex-cli@0.1.
|
|
691
|
+
These plugin installs launch the pinned npm package with `npx -y -p @cantinasecurity/apex-cli@0.1.33 apex-mcp`, so users do not need to install `apex` globally before enabling the plugin.
|
|
621
692
|
|
|
622
693
|
The repository also includes `.agents/plugins/marketplace.json` for local Codex marketplace testing from a checkout.
|
|
623
694
|
|
package/dist/apex.js
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
|
+
import { commandStartThreatModel, commandThreatModel, hasAuditSetupBeta, threatModelExitCode, terminalText } from "./threat-model.js";
|
|
2
|
+
import { getMe } from "./auth.js";
|
|
1
3
|
import { ApexApiClient, formatApiError } from "./api-client.js";
|
|
2
|
-
import { parseArgs, validateCommandFlags } from "./args.js";
|
|
3
|
-
import { commandCancelScan, commandClaim, commandConnect, commandCredits, commandDoctor, commandExportFindings, commandFindingComment, commandFindingFeedback, commandFindingFixReview, commandFindings, commandWorkspaceFindings, commandLogin, commandLogout, commandRedeem, commandScan, commandScans, commandServiceKeyCreate, commandServiceKeyList, commandServiceKeyRevoke, commandSetup, commandStatus, commandTelemetry, commandUpdate, commandWorkspace, commandWorkspaceUse, commandWorkspaces, getRedeemExitCode, } from "./commands.js";
|
|
4
|
-
import {
|
|
4
|
+
import { isAuditSetupInvocation, parseArgs, validateCommandFlags } from "./args.js";
|
|
5
|
+
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 { cliHelpText } from "./help.js";
|
|
5
7
|
import { runMcpServer } from "./mcp.js";
|
|
6
8
|
import { runInteractiveShell } from "./shell.js";
|
|
7
9
|
import { createCliTelemetryInvocation, emitInvocationCompleted, emitInvocationStarted, withTelemetryContext, } from "./telemetry.js";
|
|
8
10
|
import { maybePromptForUpdate } from "./update.js";
|
|
9
11
|
async function dispatch(parsed) {
|
|
12
|
+
const client = new ApexApiClient();
|
|
10
13
|
if (parsed.flags.help === true || parsed.command === "help") {
|
|
11
|
-
|
|
14
|
+
const me = await getMe(client, { signal: AbortSignal.timeout(1_500) }).catch(() => null);
|
|
15
|
+
process.stdout.write(cliHelpText(hasAuditSetupBeta(me)));
|
|
12
16
|
return;
|
|
13
17
|
}
|
|
14
18
|
if (parsed.command === "mcp") {
|
|
15
19
|
await runMcpServer();
|
|
16
20
|
return;
|
|
17
21
|
}
|
|
18
|
-
const client = new ApexApiClient();
|
|
19
22
|
const cwd = process.cwd();
|
|
20
23
|
if (await maybePromptForUpdate(parsed.flags, parsed.command)) {
|
|
21
24
|
return;
|
|
@@ -25,7 +28,13 @@ async function dispatch(parsed) {
|
|
|
25
28
|
await runInteractiveShell(client, cwd, parsed.flags);
|
|
26
29
|
return;
|
|
27
30
|
case "scan":
|
|
28
|
-
|
|
31
|
+
if (parsed.flags.setup !== undefined)
|
|
32
|
+
await commandStartThreatModel(client, cwd, parsed.flags);
|
|
33
|
+
else
|
|
34
|
+
await commandScan(client, cwd, parsed.flags);
|
|
35
|
+
return;
|
|
36
|
+
case "threat-model":
|
|
37
|
+
await commandThreatModel(client, cwd, parsed.flags, parsed.subcommand);
|
|
29
38
|
return;
|
|
30
39
|
case "scans":
|
|
31
40
|
await commandScans(client, cwd, parsed.flags);
|
|
@@ -94,6 +103,24 @@ async function dispatch(parsed) {
|
|
|
94
103
|
await commandWorkspace(cwd, parsed.flags);
|
|
95
104
|
return;
|
|
96
105
|
case "findings":
|
|
106
|
+
if (parsed.subcommand === "send") {
|
|
107
|
+
if (parsed.args.length !== 2)
|
|
108
|
+
throw new Error("Usage: apex findings send <finding-ref> <linear|jira>");
|
|
109
|
+
await commandFindingSend(client, cwd, parsed.flags, parsed.args[0], parsed.args[1]);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (parsed.subcommand === "tickets") {
|
|
113
|
+
if (parsed.args.length !== 1)
|
|
114
|
+
throw new Error("Usage: apex findings tickets <finding-ref>");
|
|
115
|
+
await commandFindingTickets(client, cwd, parsed.flags, parsed.args[0]);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (parsed.subcommand === "link-ticket") {
|
|
119
|
+
if (parsed.args.length !== 3)
|
|
120
|
+
throw new Error("Usage: apex findings link-ticket <finding-ref> <linear|jira> <issue-key|url>");
|
|
121
|
+
await commandFindingLinkTicket(client, cwd, parsed.flags, parsed.args[0], parsed.args[1], parsed.args[2]);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
97
124
|
if (parsed.subcommand === "comment") {
|
|
98
125
|
const findingRef = parsed.args[0] ?? "";
|
|
99
126
|
const content = parsed.args.slice(1).join(" ").trim() || String(parsed.flags.content ?? "");
|
|
@@ -152,8 +179,10 @@ async function dispatch(parsed) {
|
|
|
152
179
|
throw new Error(`Unknown command: ${parsed.command}`);
|
|
153
180
|
}
|
|
154
181
|
}
|
|
182
|
+
let parsedInvocation = null;
|
|
155
183
|
async function main() {
|
|
156
184
|
const parsed = parseArgs(process.argv.slice(2));
|
|
185
|
+
parsedInvocation = parsed;
|
|
157
186
|
validateCommandFlags(parsed);
|
|
158
187
|
const invocation = createCliTelemetryInvocation(parsed);
|
|
159
188
|
emitInvocationStarted(invocation);
|
|
@@ -184,6 +213,10 @@ main().catch((error) => {
|
|
|
184
213
|
process.exitCode = 1;
|
|
185
214
|
return;
|
|
186
215
|
}
|
|
187
|
-
|
|
188
|
-
process.
|
|
216
|
+
const setupCommand = isAuditSetupInvocation(parsedInvocation);
|
|
217
|
+
if (setupCommand && process.argv.includes("--json"))
|
|
218
|
+
process.stdout.write(`${JSON.stringify({ error: formatApiError(error), exitCode: threatModelExitCode(error) })}\n`);
|
|
219
|
+
else
|
|
220
|
+
process.stderr.write(`${setupCommand ? terminalText(formatApiError(error)) : formatApiError(error)}\n`);
|
|
221
|
+
process.exitCode = setupCommand ? threatModelExitCode(error) : 1;
|
|
189
222
|
});
|
package/dist/args.js
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
export const KNOWN_CLI_FLAG_NAMES = [
|
|
2
|
+
"setup",
|
|
3
|
+
"draft",
|
|
4
|
+
"file",
|
|
5
|
+
"sha256",
|
|
6
|
+
"request-id",
|
|
7
|
+
"wait",
|
|
8
|
+
"timeout",
|
|
9
|
+
"start-scan",
|
|
10
|
+
"no-feedback-context",
|
|
11
|
+
"attachment",
|
|
2
12
|
"comment",
|
|
3
13
|
"company",
|
|
4
14
|
"content",
|
|
@@ -35,6 +45,7 @@ export const KNOWN_CLI_FLAG_NAMES = [
|
|
|
35
45
|
];
|
|
36
46
|
const KNOWN_CLI_FLAGS = new Set(KNOWN_CLI_FLAG_NAMES);
|
|
37
47
|
const BOOLEAN_FLAGS = new Set([
|
|
48
|
+
"wait", "start-scan", "no-feedback-context",
|
|
38
49
|
"force",
|
|
39
50
|
"non-interactive",
|
|
40
51
|
"json",
|
|
@@ -44,6 +55,7 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
44
55
|
"help",
|
|
45
56
|
]);
|
|
46
57
|
const REPEATABLE_VALUE_FLAGS = new Set([
|
|
58
|
+
"attachment",
|
|
47
59
|
"repo",
|
|
48
60
|
"pr",
|
|
49
61
|
"pr-path",
|
|
@@ -96,10 +108,14 @@ export function validateCommandFlags(parsed) {
|
|
|
96
108
|
const isWorkspaceFindings = parsed.command === "findings" &&
|
|
97
109
|
parsed.subcommand === null &&
|
|
98
110
|
parsed.flags.workspace === true;
|
|
99
|
-
|
|
111
|
+
const isSetupScan = parsed.command === "scan" && parsed.flags.setup !== undefined;
|
|
112
|
+
if (!isWorkspaceFindings && !isSetupScan) {
|
|
100
113
|
throw new Error("--workspace-ref is only supported with `apex findings --workspace`. To change the local workspace used by scans or exports, run `apex workspace use <workspace-ref>` first.");
|
|
101
114
|
}
|
|
102
115
|
}
|
|
116
|
+
export function isAuditSetupInvocation(parsed) {
|
|
117
|
+
return (parsed?.command === "threat-model" || parsed?.flags.setup !== undefined);
|
|
118
|
+
}
|
|
103
119
|
export function isJsonMode(flags) {
|
|
104
120
|
return flags.json === true;
|
|
105
121
|
}
|
package/dist/auth.js
CHANGED
|
@@ -78,9 +78,9 @@ async function waitForDeviceLoginCredentials(client, options) {
|
|
|
78
78
|
expiresAt,
|
|
79
79
|
};
|
|
80
80
|
}
|
|
81
|
-
export async function getMe(client) {
|
|
81
|
+
export async function getMe(client, options = {}) {
|
|
82
82
|
try {
|
|
83
|
-
return await client.request("/api/cli/v1/me");
|
|
83
|
+
return await client.request("/api/cli/v1/me", options);
|
|
84
84
|
}
|
|
85
85
|
catch (error) {
|
|
86
86
|
if (error instanceof ApiError && error.status === 401) {
|