# publish-findings

Preview and import scanner findings, including issues selected in the SonarQube plugin, into the local workbench or Codex Security Cloud.

- **Kind:** skill
- **Source:** https://github.com/openai/codex-security
- **Page:** https://forefy.com/skills/447d4cce-aa0a-47fd-b16a-5d59f885647a
- **API (JSON + files):** https://forefy.com/api/asr/447d4cce-aa0a-47fd-b16a-5d59f885647a

## Install

Ask your agent: `Install the publish-findings skill securely from: https://forefy.com/skills/447d4cce-aa0a-47fd-b16a-5d59f885647a globally to ~/.claude/skills`

Or run the pinned install script: `curl -fsSL https://forefy.com/api/asr/447d4cce-aa0a-47fd-b16a-5d59f885647a/install | bash` (`?location=project` for the current project, `&harness=agents` for `.agents/`, `&shell=powershell` with `irm ... | iex`).

---

## SKILL.md

---
name: publish-findings
description: Preview and import scanner findings, including issues selected in the SonarQube plugin, into the local workbench or Codex Security Cloud.
---

# Publish external findings

Use `codex-security import sonar` for [live SonarQube Cloud issues](#sonarqube-cloud-issues) or a capture saved by that command. Use `codex-security publish findings` for native scanner exports, including Sonar `/api/issues/search` pages with `--provider sonar`. Select the requested destination. The CLI owns source mapping, persistence, and Cloud authentication and retries. Do not implement another store or uploader or put credentials in command arguments.

## Select the evidence

Use a supplied report, connected tool or signed-in CLI that provides the requested native records for the selected account, repository and ref. Follow the [scanner import guide](https://github.com/openai/codex-security/blob/main/docs/scanner-imports/README.md) to narrow broader reports and verify the selection in the JSON preview. Use a direct API only when the provider guide supports its response for the selected destination. Otherwise ask for a supported report. A fresh scan produces new findings. Keep the original records and complete the requested pagination. A JSON summary or a different product's API response is not interchangeable with the documented report. Use SARIF or CSV only when the installed provider reader supports that profile and native JSON is unavailable or the user supplies that format. Do not convert a richer JSON report to SARIF for import.

Use the same acquisition format for repeated imports. Check the provider's identity rules before switching formats: another format can create a separate record or replace a richer snapshot with less detail. Importing findings does not reconcile equivalent vulnerabilities across formats. `--format json` controls command output, not the input report format; readers detect their supported inputs.

## Import locally

Use the checkout path with `--repository`. A saved report can be imported locally offline. Use this destination for Tenable One Code Repository captures. [Supported inputs by provider](https://github.com/openai/codex-security/blob/main/docs/scanner-imports/README.md#choose-your-scanner)

```sh
codex-security publish findings /path/to/selected-wiz-findings.json \
  --to local --repository /path/to/project --provider wiz \
  --source-key TENANT_ID/vulnerability-finding --dry-run --format json
```

Show the repository, source namespace, normalized evidence, and exclusions. For a zero-record preview, report whether the input is empty or failed and stop. After confirmation, repeat without `--dry-run`, adding `--yes`. Keep approval when the user already approved this preview and the input and destination are unchanged.

Read the stored evidence with `codex-security findings imported /path/to/project --format json`; the repository defaults to the current directory. Report created, updated, unchanged, and excluded counts, including exclusion reasons when the command exits 1.

Local records remain unassessed source evidence, separate from `findings list` and completed scans. Repeated imports use the same repository/provider/source-key/source-ID identity; omitted records remain stored. When a source repository URL and local Git remotes are available, the URL must match a remote. Local import does not publish to Cloud or automatically investigate or patch findings.

## Trivy dependency vulnerabilities

Use `--provider trivy` with a saved schema-v2 repository or filesystem report (JSON or gzip). Preview with a stable project/branch source key and `--trivy-scan-root` relative to the checkout (`.` for the whole checkout). Omit the root only when the report's Git metadata corroborates a remote repository scan. Check the source, package paths and exclusions before importing. Trivy's `fixed` status identifies an available upstream fix. [Trivy import guide](https://github.com/openai/codex-security/blob/main/docs/scanner-imports/trivy.md)

## Amazon Inspector repository findings

Use `--provider inspector` and `--source-repository-url` with a complete Inspector2 ListFindings export for one Code Security repository. If it contains only a project ARN and name, pass the full `--inspector-project-arn ARN` verified for that Git URL in the SCM integration. Supply the same ARN when resuming a pending Cloud submission. Self-managed GitLab is supported locally.

## AWS Continuum code findings

Use `--provider continuum` and `--source-repository-url` for an AWS Continuum for Code Vulnerabilities export. Verify which repository owns each selected finding. Use `aws continuum get-code-finding` for a selected ID, or `list-code-findings` with automatic pagination when every row belongs to the requested repository. Reuse the same source key for that environment and repository. [Continuum import guide](https://github.com/openai/codex-security/blob/main/docs/scanner-imports/continuum.md)

## Black Duck SCA project-version vulnerabilities

Use `--provider blackduck` for a BOM-6 vulnerable-components export from one project version. Supply `--source-repository-url` after verifying the project version maps to that repository. Keep the same source key on replay and preview the destination before importing.

## Checkmarx SAST

Use `--provider checkmarx` for a Checkmarx One CLI JSON or self-managed CxSAST XML report. To retrieve a completed scan, follow the [Checkmarx collection instructions](references/checkmarx-import.md). Collection retrieves the full scan. If the user wants a subset, ask for a filtered vendor export. Before fetching CxSAST XML, verify the scan ID, project and owning team on the selected server.

Preview the report for the verified repository and ref with `--dry-run --format json`. After confirmation, use the same report path and source key for the import and any retries.

## Veracode Static Analysis policy findings

Use `--provider veracode` with all Findings API pages for one application’s STATIC policy findings. Verify that the application maps to the repository named by `--source-repository-url`. Keep the source key stable for that application and preview the import before writing.

## Endor Labs findings

Use authenticated `endorctl` for one verified project and context. Omit `--field-mask` to keep complete native Finding objects. Replace `/path/to/private/reports` with a private directory outside the checkout.

```sh
out=$(mktemp -d "/path/to/private/reports/endor.XXXXXX") &&
  endorctl api list --resource Finding --namespace "YOUR_NAMESPACE" \
    --filter 'context.type==CONTEXT_TYPE_MAIN and spec.project_uuid=="PROJECT_UUID"' \
    --list-all --output-type json > "$out/findings.tmp" &&
  mv "$out/findings.tmp" "$out/endor-findings.json" &&
  printf '%s\n' "$out/endor-findings.json"
```

Preview the completed file with `--provider endor` and a stable source key such as `YOUR_NAMESPACE/PROJECT_UUID/main`. Match the project to the destination repository. For another branch or scan context, change the filter and use a distinct source key. The [official Endor Labs Agent Kit for Codex](https://github.com/endorlabs/ai-plugins/tree/main/plugins/codex/endor-labs-agent-kit) can help with setup and investigation; its summaries, counts and field-masked results do not replace complete Finding objects. The [CLI API](https://docs.endorlabs.com/developers-api/cli/commands/api) supplies the export. The importer reads that saved file; it does not maintain an Endor API connection or write changes back to Endor.

Native JSON retains severity, scanned commit, package and advisory data, and primary SAST/AI SAST locations. Existing self-contained endorctl SARIF is a lossy compatibility format that retains finding UUIDs and links; unavailable severity or revisions stay unknown. Both formats use the Endor UUID within the selected repository and source key as identity. Importing SARIF after native JSON updates the same record by replacing its evidence snapshot, not merging it: native detail absent from SARIF is removed from that snapshot. Use native JSON consistently for ongoing imports. Keep the original evidence and inspect exclusions. Imported records remain unassessed source evidence. Follow the [local](#import-locally) or [Cloud](#publish-to-cloud) workflow for the chosen destination.

## Wiz findings

Read [Wiz import formats](references/wiz-import.md#finding-formats) before preparing input. Use an authorized [Wiz MCP](https://www.wiz.io/blog/introducing-wiz-mcp) or Wiz API connection; a configured plugin may provide it, but do not assume an official Codex plugin is available. Prefer supported native JSON. Inspect actual records, finish the requested pagination, and save only the selected evidence with its original occurrence IDs. A supplied export is also supported. Summaries, Wiz Issues, and local CLI scan results are not automatically supported finding records.

Validate the acquisition path against actual responses from an authorized Wiz tenant; local parser checks do not establish that qualification. Direct CSV input covers repository package vulnerabilities (SCA) only, not SAST or cloud posture findings, and does not provide a complete Wiz code-to-cloud integration.

Use a stable Wiz tenant/finding namespace as `--source-key`; a project filter does not create a new identity. Confirm the selected assets or builds belong to the destination repository. Do not infer source branches or revisions from the current checkout, invent missing evidence, or import vendor credentials, unrelated records, or local assessments as original scanner evidence. Do not retrieve or test credentials or apply suggested fixes.

## Snyk findings

For an existing monitored Open Source or Code project, follow the [Snyk Issues API import guide](https://github.com/openai/codex-security/blob/main/docs/scanner-imports/snyk.md#existing-issues-from-snyk). With a read-authorized `SNYK_TOKEN`, run `codex-security import snyk PROJECT_ID --snyk-org ORG_ID --source-repository-url URL --format json`. Add `--snyk-product code` for a Code project and `--snyk-api-origin` for a regional deployment. Save the complete successful JSON output, preview with `publish findings --provider snyk` and the requested `--to local` or `--to cloud` destination, then confirm the import.

Prefer the [official Snyk Codex plugin](https://github.com/snyk/studio-recipes/tree/main/plugins/codex) when available. Ask its `snyk-findings-export` skill for **SCA only from the repository’s Git root** and save the `snyk-findings-export/v1` bundle. It scans the current workspace through local MCP; it cannot list existing platform projects or retrieve earlier findings. Do not substitute a fresh scan for a request to read a platform backlog. Authentication, relevant Snyk access and the project's package tooling are required.

Import the bundle with `--provider snyk`, choosing `--to local --repository /path/to/project` or `--to cloud --repository https://github.com/example/project`. Use a stable repository/branch/scan-target/acquisition namespace such as `github.com/example/project/main/root/plugin-sca`. Check the bundle's scan outcomes, collection completeness and limitations alongside the preview. Only Open Source SCA vulnerability records are supported; other categories are explicit exclusions. Preserve the native MCP record and unavailable fields. MCP descriptions may be shortened and alternate dependency paths omitted; do not reconstruct missing evidence. Plugin v1 paths are target-relative without a Git-root offset, so the importer keeps them in original evidence instead of repository locations. Use a distinct source key for each scan target. Ignore status is a source policy decision, not a Codex assessment or a fix.

For CLI automation, prefer `snyk test --file=package.json --json-file-output=snyk-open-source.json` from the selected repository root. Exit 1 means vulnerabilities were found; stop on other nonzero exits instead of importing a stale file. The existing `--provider snyk` autodetects native JSON (one project or an `--all-projects` array), plugin JSON and Open Source SARIF. Native JSON preserves per-dependency-path records and remediation fields; do not use `--group-issues`. Project errors stop import. Native JSON paths also lack a Git-root offset and stay in original evidence. These workspace scan formats import Open Source SCA vulnerabilities.

Use one format consistently per repository, branch and scan target, with a stable source key such as `github.com/example/project/main/root/native-sca`. Plugin, native JSON and SARIF have distinct identities; use separate keys when switching, without claiming cross-format deduplication or removal of older records. Existing `snyk test --sarif-file-output=snyk-open-source.sarif` reports remain a compatibility path. SARIF groups advisory occurrences and exposes one package coordinate, so prefer JSON for acquisition. Its target-relative manifest paths lack a Git-root offset and stay in original evidence with synthetic line numbers, without being projected as repository locations or package manifest paths.

Retain package versions, reported fixed versions, provenance and original evidence without claiming a fix was applied. Known repository claims must match the selected Cloud repository or, when available, a configured local Git remote. Resolve an unknown repository with the user; never infer that an export belongs to the current checkout. Follow [local imports](#import-locally) for preview, confirmation and readback or [Cloud publication](#publish-to-cloud) for upload authorization. Snyk workspace reports and monitored-project exports support local and Cloud import.

## Semgrep Code

Prefer native Code/SAST CLI or CI JSON for this reader; use self-contained SARIF 2.1.0 as a compatibility path. JSON retains the original severity better than SARIF. Use the selected Guardian workflow below for existing Code or Supply Chain findings. Select SAST rules or Code-only CI output; identifiable non-SAST records are excluded, but SARIF may omit the product metadata needed to distinguish Secrets.

For a user-requested new scan, use the installed Semgrep CLI from the source repository root with the selected SAST rules: `semgrep scan --config /path/to/sast-rules.yaml --json-output /path/to/semgrep.json .`. Existing CI jobs can produce the same artifact using `--json-output`; authenticated `semgrep ci --code` follows the job's policy and baseline scope. Explain that this generates fresh scan findings, and do not substitute it for a request to retrieve existing Platform findings.

For a selection of existing Code or Supply Chain findings in Codex, use the signed-in [Semgrep Guardian plugin](https://docs.semgrep.dev/semgrep-guardian/quickstart). Call its `get_semgrep_sast_findings` or `get_semgrep_supply_chain_findings` tool with the selected `deployment_name`, `repository_name` and full `ref`. Preserve any state and severity filters used for that selection. Reuse an existing successful tool result when its scope is explicit. If a selected ID is missing, report it and check the query scope and filters with the user. A project-wide fetch is a separate request.

Save a private JSON capture outside the checkout with `format: "semgrep-guardian-selection/v1"`, the verified `sourceRepositoryUrl`, the MCP `request` (tool `name` and `arguments`), the exact selected string `findingIds`, and `structuredContent: { findings: [...] }` containing the original selected tool rows. Match IDs exactly and keep the tool's repository, ref and evidence. Guardian returns a repository name, so verify its SCM URL for the local checkout or selected Cloud repository. Use the [Guardian preview command](https://github.com/openai/codex-security/blob/main/docs/scanner-imports/semgrep.md#import-selected-guardian-findings) for the intended destination and confirm after reviewing the selection. Guardian handles sign-in for this workflow. Guardian and Web API IDs remain separate even when the strings match.

For a whole project and ref, use the [Web API collector](https://github.com/openai/codex-security/blob/main/docs/scanner-imports/semgrep.md#collect-a-platform-project-and-branch) with its read-capable token. If the user supplies [saved Web API responses](https://github.com/openai/codex-security/blob/main/docs/scanner-imports/semgrep.md#existing-platform-findings), use the IDs from those records. Preview Web API findings against the requested local or Cloud repository.

Import the native report with `--provider semgrep`, a stable organization/project/branch `--source-key`, and `--source-repository-url` identifying the actual source repository. Use `--to local --repository /path/to/project` for local storage or `--to cloud --repository URL_OR_ID` for Cloud. The source URL is checked against configured local Git remotes or the Cloud destination. Preview with `--dry-run --format json` before confirming the import.

Keep supplied fingerprints. Reimporting the same fingerprint after code moves updates the existing source record with the new range. If the fingerprint is absent or `requires login`, the fallback uses rule, literal path and range: movement creates a second retained record, and the earlier record remains stored. Do not infer a merge or deletion. Read the persisted evidence with `codex-security findings imported /path/to/project --format json` before assessment. Source rule, CWE, snippets, traces and status are vendor evidence, not Codex assessments. Do not infer a report revision from the current checkout. Resolve scan errors and review all exclusions before uploading; do not remove error metadata to force acceptance.

## OpenGrep

Import native OpenGrep JSON with `--provider opengrep`, `--source-repository-url`, and a stable OpenGrep-specific `--source-key`. Preview the destination and scan diagnostics before importing. Local and Cloud imports use the Semgrep provider and retain OpenGrep as the scanner origin.

## SonarQube Cloud issues

For “import these Sonar findings,” carry over the Sonar project, branch or PR, and the exact issue keys selected in the [SonarQube plugin for Codex](https://www.sonarsource.com/blog/now-available-sonarqube-plugin-for-codex/). Use the keys from the underlying Sonar result. If only a formatted table remains, resolve the selected rows against the same project and ref and ask about ambiguous matches. A rule can occur more than once. Never turn a selection into a project-wide import.

From the intended checkout, preview the selection using the developer's authenticated `sonar` CLI. Use `--sonar-pull-request` in place of `--sonar-branch` for a PR. Omit `--sonar-issue` only when the user wants the project's open security issues. If the project has no verified SCM binding, confirm the repository URL that belongs to the project and pass it with `--source-repository-url`.

```sh
codex-security import sonar --sonar-project example_project --sonar-branch main \
  --sonar-issue ISSUE_A --sonar-issue ISSUE_B --dry-run --format json
```

Pass `--to cloud` at preview time if the user also requested Cloud publication. Show the selected issues and exclusions, the local repository, and any Cloud destination as described under [Publish to Cloud](#publish-to-cloud). The preview returns a private evidence file in `capture`. After confirmation, run `codex-security import sonar CAPTURE --yes` for local import. For Cloud, use the exact `capture` from the successful Cloud preview and add `--to cloud`. If the account, repository, or environment changes, show a fresh preview and ask for confirmation. If Cloud is unavailable, finish the requested local import and report the Cloud error. For a request to publish an existing import, start with its capture. Report the saved path so the developer can resume.

Sonar authentication is required only for a live fetch. If the local CLI lacks a login, direct the user to `sonar auth login` and keep the selection. A hosted MCP connection can help identify issues, but its issue summaries omit flow evidence. Use the signed-in CLI or a saved Codex Security Sonar capture to complete the import. If `codex-security` is absent, ask the user to install it from a trusted directory and invoke that installed executable in the checkout.

## GitHub alerts and advisories

Acquire code-scanning findings with `codex-security import github OWNER/REPO --format json` and save its complete JSON output for local import or Cloud publication. This existing collector completes pagination, fetches alert details, and retains the selected ref's instance. In Codex, use the official [GitHub Codex plugin](https://github.com/openai/plugins/tree/main/plugins/github) for discovery and selecting alerts, then pass that selection to the collector. Its GitHub MCP server paginates each list call separately. Do not ask the user to download JSON from a website or reconstruct a code-scanning import envelope from MCP summaries. Use the saved collector output for code-scanning alerts.

The importer uses the user's GitHub CLI authentication and `GH_HOST`; plugin authentication is separate. It defaults to open alerts on the default branch. Use repeated `--github-alert NUMBER` for exact alerts or `--github-ref REF` for another ref. Save complete output without field/token filters or `--full-output`. Reading code-scanning alerts requires a token with Code scanning alerts read permission (classic: `security_events`, or `public_repo` for public repositories), along with repository access.

For Dependabot, follow the [repository alert export guide](https://github.com/openai/codex-security/blob/main/docs/scanner-imports/github-dependabot.md) to save the native REST alert records from one repository. The read-only export requires Dependabot alerts read permission. Preview the file with `publish findings FILE --provider github`, the intended destination and a separate source key such as `github.com/example/project/dependabot`.

**GHSA import stages advisory evidence for investigation; it does not establish an installed dependency or applicability to the destination repository.** Keep advisories separate from code scanning. `codex-security import github-advisories OWNER/REPO --format json` reads published repository disclosures by default. Repeated `--github-advisory GHSA-xxxx-xxxx-xxxx` selects exact IDs; `--github-state draft|triage|closed` explicitly selects an unpublished list and cannot be combined with exact IDs. Add `--github-advisory-source global` for exact public database IDs. This does not enumerate the global database. Private advisory access needs Repository security advisories read permission and access to those records. Stop on a read failure instead of publishing an incomplete selection.

Save the selected collector’s output in a private file outside the checkout. After collection succeeds, use `--provider github` and the [local](#import-locally) or [Cloud](#publish-to-cloud) workflow for the requested destination. Keep a separate stable source key for each family, such as `github.com/example/project/code-scanning` or `github.com/example/project/security-advisories`.

Code-scanning identities include the ref; repository alerts and advisories must match the selected Cloud repository or, when available, a configured local Git remote. Global advisories omit source-repository provenance and are bound to the explicit local or Cloud destination for investigation. Preserve original evidence, package ranges and patches; do not infer installed packages or source locations from an advisory. Missing security severity stays `unknown`. Dismissal, resolution and withdrawal are source state, not Codex assessment. Secret-scanning alerts and writeback to GitHub are outside this workflow.

## Publish to Cloud

Resolve the user's destination to a connected GitHub or GitHub Enterprise repository URL or Cloud repository ID, including custom Enterprise hosts. Confirm the intended Cloud account and authorized repository environment before running a preview that can contact Cloud. Use the existing file-backed ChatGPT login; explain a missing or incompatible login rather than changing credential storage automatically.

For an alternate Cloud deployment, configure the [deployment settings](#deployment-settings) before previewing. Keep the selected deployment and login throughout the upload.

### Preview and approve

```sh
codex-security publish findings /path/to/selected-wiz-findings.json \
  --to cloud --repository https://github.com/example/project --provider wiz \
  --source-key TENANT_ID/vulnerability-finding --dry-run --format json
```

When the preview returns `status: "empty"`, report that there is nothing to upload. For a nonempty preview, show the endpoint, account, repository, source key, severity counts and exclusions. The `privateImport` entry describes the report for the user’s private inbox. For the repository publisher, `requests` identifies the environments that will receive the records. Identify the evidence file and make the complete JSON preview available for inspection without pasting a large report into the conversation. Explain that vendor evidence is not a Codex assessment.

Obtain explicit confirmation for this concrete upload, including skipped records. Then run the same command without `--dry-run`, adding `--yes`. Authorization to inspect or assess findings alone does not authorize sending them to Cloud. Keep approval when the user already approved this preview and the input and destination are unchanged.

### Upload and report

Read the structured result even when the process exits nonzero. Progress appears on stderr. For a private inbox import, check `privateJob.status`, the job counts, errors and unprocessed records. A completed job can have `verified: 0`.

For the repository publisher, report `verified` for findings the CLI confirmed by readback and `unacknowledged` for records without a receipt. Use `failures` to identify records the publisher rejected. For either destination, include exclusions and any `savedSubmission` checkpoint. [Result fields and exit codes](https://github.com/openai/codex-security/blob/main/sdk/typescript/docs/cli.md#results-and-exit-codes)

A private finding can be assessed against the repository and published to shared Findings. Link to the returned `cloudUrl` when available.

### Resume an interrupted upload

Preview the original file again with the same source key, repository, account, endpoint and state directory. If `resumed` is true, the CLI reuses the saved submission. If a reset or changed destination requires a new submission, review the new preview before confirming. [Retry a Cloud submission](https://github.com/openai/codex-security/blob/main/docs/scanner-imports/schema-guide.md#retry-a-cloud-submission)

### Deployment settings

Set `CODEX_SECURITY_CLOUD_BASE_URL` to the deployment's API root for preview and upload. Use its login and an isolated state directory for tests. If the user or Cloud page supplies an app URL, set `CODEX_SECURITY_CLOUD_WEB_URL` to that full URL. This setting controls the returned UI link. The API root and login still determine routing and authentication.

Check the preview's `cloudApiUrl` and any `cloudUrl` against the intended API endpoint and app. Saved retries are scoped to the deployment. `CODEX_SECURITY_CLOUD_PUBLISH_URL` applies only to native scan publication.

## agents

```

```

## agents/openai.yaml

```yaml
interface:
  display_name: "Publish Findings"
  short_description: "Import vendor findings locally or to Cloud"
  default_prompt: "Use $publish-findings to preview selected vendor findings for my repository and import them to my chosen destination."
policy:
  allow_implicit_invocation: true
```

## references

```

```

## references/checkmarx-import.md

# Collect and import Checkmarx SAST findings

Use a supplied One CLI JSON or CxSAST XML report directly. For collection, confirm the product, tenant or server, completed scan ID, project, repository URL and ref. Both collectors retrieve the full scan. If the operator wants a subset, ask for an export scoped to that selection.

## Before launching a vendor tool

- Use a trusted session and a private working and temporary directory outside the source tree, for both local and Cloud imports. Set the working directory **before** starting a process. Use a noninteractive, non-login system shell such as `/bin/sh` with `login: false`.
- If an IDE or build supplies checkout-controlled startup settings, use a known-clean session or a saved export. A trusted host can exclude `LD_*`, `DYLD_*`, `BASH_ENV` and `ENV` before launching a shell or interpreter. Keep existing exclusions and account for restored shell snapshots. Never start a suspect shell to unset variables or source project files. [Codex shell environment policy](https://developers.openai.com/codex/config-advanced/)
- Resolve the installed [collector](../scripts/collect_checkmarx.py) and vendor tools to trusted absolute paths outside the source tree. For `python_path`, use trusted system tools to inspect the executable and resolve symlinks without running it. Use the native Python 3.10+ interpreter with `-I -S`. Ask for native installation paths if given a shim or shell launcher.
- Pass the absolute checkout or unpacked source as `--source-root` before the product. Omit it only when there is no local source tree.

## Checkmarx One

Use native `cx` 2.3.23+. An explicit `CX_CONFIG_FILE_PATH` selects that trusted profile. Without an explicit profile, the helper uses a complete environment login if present, or `~/.checkmarx/checkmarxcli.yaml`. For an environment login, confirm the destination and keep `CX_APIKEY` or `CX_CLIENT_ID`, `CX_CLIENT_SECRET`, `CX_BASE_URI` and `CX_TENANT` in the operator's environment. A separate OAuth server also uses `CX_BASE_AUTH_URI`. For an older client or a credential-store-only login with no profile file, ask for JSON exported with the operator’s usual setup.

`cx results show` retrieves every engine in a mixed scan and can generate and download an SCA package report. [Checkmarx One results](https://docs.checkmarx.com/en/34965-68640-results.html)

```sh
"$python_path" -I -S "$collector" --source-root "$source_root" one "$scan_id" --cx "$cx_path"
```

## Self-managed CxSAST

**Verify ownership before fetching.** CxFlow requests XML by scan ID. Confirm the completed ID, exact project and full owning team path from the same **Projects & Scans → All Scans** row on the selected server. Use trusted read-only metadata or ask the operator to confirm those values from that view. For repeated names, check **View Project Scans** under the intended team. Without confirmation, ask for selected XML. Issue-tracker filters do not narrow the raw XML. [All Scans](https://docs.checkmarx.com/en/34965-46419-viewing-results-from-all-scans.html)

Use native Java, a released CxFlow JAR (1.6.37+), and a trusted SAST-only profile with `CxXml` enabled. Keep the profile and everything it loads outside the checkout. Take the server, authentication and any custom `checkmarx.client-id` from that profile. Only append `--use-env-credentials` when the operator confirms the session's `CHECKMARX_USERNAME`, `CHECKMARX_PASSWORD` or `CHECKMARX_CLIENT_SECRET` belong to that server. For custom JVM settings or project names the installed CxFlow cannot resolve, ask for an XML export. [CxFlow credentials](https://github.com/checkmarx-ltd/cx-flow/blob/v1.7.14/docs/Tutorials.md#L382-L390) · [CxXml writer](https://github.com/checkmarx-ltd/cx-flow/blob/v1.7.14/docs/Bug-Trackers-and-Feedback-Channels.md#cxxml)

```sh
"$python_path" -I -S "$collector" --source-root "$source_root" cxsast "$scan_id" \
  --java "$java_path" --jar "$jar_path" --config "$config_path" --team "$cx_team" --project "$cx_project"
```

## Preview and import

Pass the successful helper's printed path as the quoted file argument to `codex-security publish findings` in the next tool call. Use `--provider checkmarx`, the requested destination, verified `--source-repository-url`, a stable product/tenant/project/ref `--source-key`, and `--dry-run --format json`. Review the scope, findings and exclusions. Verify native paths against the checkout before opening source files.

After confirmation, reuse that file and those options for [local import](../SKILL.md#import-locally), [Cloud publication](../SKILL.md#publish-to-cloud) and retries. Remove the helper’s temporary directory after readback unless the operator wants it retained. Keep operator-supplied files. [User guide](https://github.com/openai/codex-security/blob/main/docs/scanner-imports/checkmarx.md)

## references/wiz-import.md

# Wiz import reference

## Finding formats

The importer reads JSON, normalized JSONL, and the repository package-vulnerability CSV profile below, with gzip supported for each format. Direct CSV support covers repository package vulnerabilities (SCA) only. It does not fetch from Wiz or accept other CSV profiles, scan events, workload secrets, cloud configuration, or external network exports. Complete the requested pages; for an explicit subset, preserve the selection and its named collection.

Keep raw source records unchanged in `source_data`. Use the vendor occurrence ID, not a rule, CVE, title, or location. Missing IDs, severity, revisions, and locations must not be invented.

For Cloud preview, approval, upload, and recovery, follow the [Cloud publication workflow](../SKILL.md#publish-to-cloud).

### Package vulnerabilities

Supported inputs include a vulnerability finding, an array, `data.vulnerabilityFindings.nodes`, a downloaded vulnerability report, and JSONL package records. Keep `id`, `name`, `detailedName`, `vendorSeverity` (or `severity`), and `vulnerableAsset.id`, with available package and source metadata. Use the bare Wiz finding ID for a package occurrence.

For a UI export, use **Vulnerability Findings → Save as → Report**, retaining repository and finding filters. Select **Repository Branch**, **JSON** or comma-separated **CSV**, and **Detailed** columns. Pass supported CSV reports directly to the command. Download the completed report; gzip is supported regardless of the filename extension. **Raw Event** on Code & Build Scans contains scan metadata, not findings. Do not create scheduled reports, notifications, or external storage unless requested.

The CSV profile requires `ID`, `Name`, `DetailedName`, `AssetID`, `WizURL`, `CloudPlatform`, and `CloudProviderURL`. `CloudPlatform` must identify GitHub, and `CloudProviderURL` must be a repository-branch URL. Its repository must match the selected Cloud repository or, for local import, a Git remote when parseable remotes are available. The importer uses the reported `ID` as occurrence identity and retains every original cell, including unknown columns, in `source_data.row`. It does not derive a branch or revision from the tree URL. Review excluded rows in the preview before importing.

### SAST, repository secrets, and IaC

Retain the named collection; bare arrays cannot identify these finding families.

| Family             | Collection                   | Occurrence ID |
| ------------------ | ---------------------------- | ------------- |
| SAST               | `data.sastFindings.nodes`    | `sast:<id>`   |
| Repository secrets | `data.secretInstances.nodes` | `secret:<id>` |
| IaC                | `data.iacFindings.nodes`     | `iac:<id>`    |

Supply a verified `repository.url` in the source repository metadata or matching `repository.id` and `repository.url` in `data.versionControlResources.nodes`. For secrets, the repository is `resource.typedProperties.repository` and the resource type must be `REPOSITORY_BRANCH`. Keep inventory `platform`: explicit `GITHUB` enables case-insensitive matching on custom GitHub Enterprise hosts.

Resolve conflicting platform or URL metadata for a repository ID before publishing. Names, inventory node IDs, `providerID`, and Wiz UUIDs are not Cloud repository IDs. The importer checks source URLs against the selected destination; fix missing or conflicting mappings before upload.

### Normalized JSONL and CSV conversion

Normalized JSONL has one record per line with `source_finding_id` and `evidence` containing `title`, `severity`, and the original record in `source_data`. For SAST, secrets, and IaC, also set `evidence.details.kind` to `sast`, `secret`, or `iac` and set the verified `evidence.details.repository.url`. Use the same prefixed occurrence IDs as raw API imports.

SAST, repository-secret, and IaC CSV reports require a reviewed mapping of their actual headers into that profile. Preserve these family-specific details when present:

| Family             | Evidence to retain                                                                                                                 |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
| SAST               | Rule/CWE, source range and snippet, scanner origin and verdict, remediation instructions                                           |
| Repository secrets | Source path and line, detector rule, type and confidence, vendor validation status, encryption/management flags, introduced commit |
| IaC                | Rule IDs/name, source range and matched content, platform, expected/actual content, file link, remediation instructions            |

Select source paths and line numbers explicitly for secret reports; the standard metadata query lacks them. Preserve empty IaC comparison strings. Vendor status, AI verdicts, and secret validation remain vendor evidence, not Codex assessments. An introduced commit is not a scanned revision, and a logical IaC resource does not prove runtime deployment.

## scripts

```

```

## scripts/collect_checkmarx.py

```python
"""Collect a selected existing scan using the user's installed Checkmarx tools."""

import argparse
import json
import os
import re
import shutil
import signal
import stat
import subprocess
import sys
import tempfile
import zipfile
from contextlib import suppress
from email.parser import BytesHeaderParser
from pathlib import Path
from xml.parsers import expat


def is_link(info: os.stat_result) -> bool:
    return stat.S_ISLNK(info.st_mode) or (
        os.name == "nt" and info.st_reparse_tag == stat.IO_REPARSE_TAG_MOUNT_POINT
    )


def source_root(target: Path | None) -> Path | None:
    if target is not None and (not target.is_absolute() or not target.is_dir()):
        raise ValueError("Select an existing absolute source directory.")
    source = (target or Path.cwd()).resolve()
    # A nested checkout or submodule is still part of the enclosing source tree.
    for parent in (*reversed(source.parents), source):
        marker = parent / ".git"
        try:
            info = marker.lstat()
            if stat.S_ISDIR(info.st_mode) and not is_link(info):
                info = (marker / "HEAD").lstat()
        except (FileNotFoundError, NotADirectoryError):
            continue
        if stat.S_ISREG(info.st_mode) or is_link(info):
            return parent
    return source if target is not None else None


def in_source(path: Path, source: Path | None) -> bool:
    if source is None:
        return False
    if path.is_relative_to(source):
        return True
    identity = source.stat()
    current = Path()
    parts = list(reversed(path.absolute().parts))
    active_links = set()
    while parts:
        part = parts.pop()
        if isinstance(part, Path):
            # The link target's components have all been checked.
            active_links.remove(part)
            continue
        current = current.parent if part == ".." else current / part
        if current == source:
            return True
        try:
            info = current.lstat()
        except (FileNotFoundError, NotADirectoryError):
            continue
        if os.path.samestat(info, identity):
            return True
        if is_link(info):
            if current in active_links:
                raise OSError(f"Symbolic link loop at {current}")
            active_links.add(current)
            parts.append(current)
            parts.extend(reversed(current.readlink().parts))
            current = current.parent
    return False


def installed_file(value: str, source: Path | None) -> Path:
    path = Path(value)
    if path.is_absolute() and in_source(path, source):
        raise ValueError("use an installed file outside the source checkout")
    if not path.is_absolute() or not path.is_file():
        raise ValueError("expected an absolute path to an existing file")
    # Invoke the selected name for native multicall binaries.
    return path.parent.resolve() / path.name


def installed_launcher(value: str, source: Path | None) -> Path:
    path = installed_file(value, source)
    if sys.platform == "win32" and (
        path.suffix.lower() not in {".exe", ".com"}
        or path.resolve().suffix.lower() in {".bat", ".cmd"}
    ):
        raise ValueError("use a native Windows executable (.exe or .com)")
    with path.open("rb") as image:
        magic = image.read(4)
        if sys.platform == "win32":
            # The DOS header points to the PE signature.
            image.seek(0x3C)
            pe_offset = int.from_bytes(image.read(4), "little")
            image.seek(pe_offset)
            native = magic.startswith(b"MZ") and image.read(4) == b"PE\0\0"
        elif sys.platform == "darwin":
            # 32/64-bit thin and universal Mach-O, in either byte order.
            native = any(
                int.from_bytes(magic, order) in {0xFEEDFACE, 0xFEEDFACF, 0xCAFEBABE, 0xCAFEBABF}
                for order in ("big", "little")
            )
        else:
            native = magic == b"\x7fELF"
    if not native:
        raise ValueError("use the installed native executable for cx or java")
    return path


def check_cxflow_version(jar: Path) -> None:
    # Earlier CxFlow versions accept --scanId but silently retrieve the latest scan.
    advice = "Use a released CxFlow 1.6.37 or later, or supply a scoped CxSAST XML export."
    try:
        with zipfile.ZipFile(jar) as archive:
            manifest = BytesHeaderParser().parsebytes(archive.read("META-INF/MANIFEST.MF"))
            metadata = archive.read("META-INF/build-info.properties").decode("utf-8")
    except (KeyError, UnicodeDecodeError, zipfile.BadZipFile) as error:
        raise ValueError(
            f"Could not read the CxFlow release metadata: {error}. {advice}"
        ) from error
    properties = dict(line.split("=", 1) for line in metadata.splitlines() if "=" in line)
    if (
        manifest.get("Start-Class") != "com.checkmarx.flow.CxFlowApplication"
        or properties.get("build.group") != "com.checkmarx"
    ):
        raise ValueError(f"The selected JAR does not identify itself as CxFlow. {advice}")
    version = properties.get("build.version", "")
    if not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+", version) or tuple(
        map(int, version.split("."))
    ) < (1, 6, 37):
        raise ValueError(f"Unsupported CxFlow version {version or '(unknown)'}. {advice}")


def private_environment(work: str, inherit: tuple[str, ...]) -> dict[str, str]:
    env = {name: os.environ[name] for name in inherit if name in os.environ}
    return env | dict.fromkeys(("TEMP", "TMP", "TMPDIR"), work)


def cxsast_environment(work: str, use_env_credentials: bool = False) -> dict[str, str]:
    # CxFlow uses the chosen profile for its server and Spring configuration.
    credentials = ("CHECKMARX_USERNAME", "CHECKMARX_PASSWORD", "CHECKMARX_CLIENT_SECRET")
    env = private_environment(work, credentials if use_env_credentials else ())
    if os.name == "nt":
        env["SystemRoot"] = os.environ["SystemRoot"]
    else:
        # The Java launcher decodes native arguments before applying file.encoding.
        env["LC_CTYPE"] = "UTF-8" if sys.platform == "darwin" else "C.UTF-8"
    return env


def one_environment(source: Path | None, work: str) -> dict[str, str]:
    # An inherited endpoint or proxy must never receive credentials from a saved profile.
    account = ("HOME", "USER", "LOGNAME", "USERPROFILE", "APPDATA", "LOCALAPPDATA", "SystemRoot")
    env = private_environment(work, (*account, "LANG", "LC_ALL", "LC_CTYPE"))
    configured = os.environ.get("CX_CONFIG_FILE_PATH")
    oauth = ("CX_CLIENT_ID", "CX_CLIENT_SECRET", "CX_BASE_URI", "CX_TENANT")
    api_key = os.environ.get("CX_APIKEY")
    if not configured and (api_key or all(os.environ.get(k) for k in oauth)):
        # A separate config path also separates the vendor's path-keyed credential store.
        profile = Path(work, "env-auth.yaml")
        profile.write_text("{}\n", encoding="utf-8")
        env["CX_CONFIG_FILE_PATH"] = str(profile)
        env["CX_KEYRING_MODE"] = "disabled"
        credentials = ("CX_APIKEY",) if api_key else (*oauth, "CX_BASE_AUTH_URI")
        env.update((name, os.environ[name]) for name in credentials if os.environ.get(name))
        return env

    profile = Path(configured) if configured else Path.home() / ".checkmarx" / "checkmarxcli.yaml"
    if not profile.is_absolute() or in_source(profile, source):
        raise ValueError(
            "Set CX_CONFIG_FILE_PATH to a trusted absolute path outside the source checkout. "
            "The collector runs Checkmarx One from a private directory."
        )
    env["CX_CONFIG_FILE_PATH"] = configured or str(profile)
    if "CX_KEYRING_MODE" in os.environ:
        env["CX_KEYRING_MODE"] = os.environ["CX_KEYRING_MODE"]
    return env


def verify_one(report: Path, scan_id: str) -> None:
    with report.open(encoding="utf-8-sig") as stream:
        data = json.load(stream)
    reported = data.get("scanID") if isinstance(data, dict) else None
    if (
        not isinstance(reported, str)
        or not reported.isascii()
        or reported.lower() != scan_id.lower()
    ):
        raise ValueError("The Checkmarx One report does not match the requested scan ID.")


def team_path(value: str) -> str:
    path = value.replace("\\", "/").strip("/")
    return "".join(char.lower() if char.isascii() else char for char in path)


def verify_cxsast(report: Path, scan_id: str, project: str, team: str) -> None:
    parser = expat.ParserCreate()

    def check_root(name, attributes):
        if name != "CxXMLResults" or not attributes.get("ProjectId"):
            raise ValueError("Expected a CxSAST XML scan report.")
        if attributes.get("ScanId") != scan_id or attributes.get("ProjectName") != project:
            raise ValueError("The CxSAST report does not match the requested scan and project.")
        actual_team = attributes.get("TeamFullPathOnReportDate")
        if not actual_team or team_path(actual_team) != team_path(team):
            raise ValueError("The CxSAST report does not match the requested team.")
        parser.StartElementHandler = None

    parser.StartElementHandler = check_root
    with report.open("rb") as stream:
        parser.ParseFile(stream)


def capture_directory(temp: Path) -> Path:
    if os.name != "nt":
        return Path(tempfile.mkdtemp(prefix="checkmarx-report-", dir=temp)).resolve()

    import ctypes.wintypes
    import secrets

    class SecurityAttributes(ctypes.Structure):
        _fields_ = [
            ("length", ctypes.wintypes.DWORD),
            ("descriptor", ctypes.wintypes.LPVOID),
            ("inherit", ctypes.wintypes.BOOL),
        ]

    # Older Windows Python releases ignore mkdir's 0700 mode. Apply CPython's
    # protected, inheritable SYSTEM/Administrators/owner DACL at creation.
    kernel = ctypes.WinDLL("kernel32", use_last_error=True)
    temp = temp.resolve()
    volume = ctypes.create_unicode_buffer(32768)
    flags = ctypes.wintypes.DWORD()
    if not kernel.GetVolumePathNameW(str(temp), volume, len(volume)):
        raise ctypes.WinError(ctypes.get_last_error())
    if not kernel.GetVolumeInformationW(volume, None, 0, None, None, ctypes.byref(flags), None, 0):
        raise ctypes.WinError(ctypes.get_last_error())
    if not flags.value & 0x8:  # FILE_PERSISTENT_ACLS
        raise ValueError("Select a temporary directory on a Windows volume that supports ACLs.")

    security = ctypes.WinDLL("advapi32", use_last_error=True)
    descriptor = ctypes.wintypes.LPVOID()
    dacl = "D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;FA;;;OW)"
    if not security.ConvertStringSecurityDescriptorToSecurityDescriptorW(
        dacl, 1, ctypes.byref(descriptor), None
    ):
        raise ctypes.WinError(ctypes.get_last_error())
    try:
        path = temp / f"checkmarx-report-{secrets.token_hex(16)}"
        attributes = SecurityAttributes(ctypes.sizeof(SecurityAttributes), descriptor, False)
        if not kernel.CreateDirectoryW(str(path), ctypes.byref(attributes)):
            raise ctypes.WinError(ctypes.get_last_error())
        return path
    finally:
        kernel.LocalFree(descriptor)


def create_capture(source: Path | None) -> Path:
    # gettempdir() probes candidates by writing a file. Check each candidate before
    # trying to create the actual capture directory in Python's platform order.
    cached = tempfile.tempdir
    candidates = [cached] if cached is not None else tempfile._candidate_tempdir_list()
    last_error = None
    for candidate in candidates:
        temp = Path(os.fsdecode(candidate)).absolute()
        if in_source(temp, source):
            raise ValueError(
                "Select a temporary directory outside the source checkout (TMPDIR or TEMP)."
            )
        try:
            return capture_directory(temp)
        except OSError as error:
            if cached is not None:
                raise
            last_error = error
    raise FileNotFoundError(
        f"No usable temporary directory outside the source checkout: {last_error}"
    )


def export_report(args: argparse.Namespace, source: Path | None, work: str, run_vendor) -> Path:
    def fetch(report, env, *command):
        # Only the validated report path goes to stdout; vendor diagnostics go to stderr.
        status = run_vendor(command, cwd=work, env=env, stdin=subprocess.DEVNULL, stdout=sys.stderr)
        if status:
            raise ValueError(f"The Checkmarx tool exited with status {status}.")
        if report.is_symlink() or not report.is_file():
            raise ValueError("The Checkmarx tool did not produce the requested report.")

    if args.product == "one":
        env = one_environment(source, work)
        report = Path(work, "cx_result.json")
        fetch(
            report,
            env,
            str(args.cx),
            "results",
            "show",
            # Older clients reject this flag instead of authenticating with another profile.
            "--config-file-path",
            env["CX_CONFIG_FILE_PATH"],
            "--scan-id",
            args.scan_id,
            "--report-format",
            "json",
            "--output-path",
            work,
            "--output-name",
            report.stem,
        )
        verify_one(report, args.scan_id)
        return report

    team = args.team.rstrip("/\\")
    report = Path(work, "checkmarx-results.xml")
    fetch(
        report,
        cxsast_environment(work, args.use_env_credentials),
        str(args.java),
        f"-Djava.io.tmpdir={work}",
        f"-DLOG_PATH={work}",
        "-Dfile.encoding=UTF-8",
        "-jar",
        str(args.jar),
        f"--spring.config.location={args.config.as_uri()}",
        "--logging.config=classpath:logback-spring.xml",
        f"--logging.file.path={work}",
        f"--logging.file.name={Path(work, 'cx-flow.log')}",
        f"--logging.logback.rollingpolicy.file-name-pattern={Path(work, 'cx-flow.%d{yyyy-MM-dd}.%i.gz')}",
        "--project",
        f"--scanId={args.scan_id}",
        f"--cx-team={team}",
        f"--cx-project={args.project}",
        f"--app={args.project}",
        "--bug-tracker=CxXml",
        "--cx-flow.bug-tracker-impl=CxXml",
        "--cx-flow.enabled-vulnerability-scanners=sast",
        "--cx-flow.preserve-project-name=true",
        "--cx-flow.mail.notification=false",
        "--cx-flow.filter-script=",
        "--disable-break-build",
        "--checkmarx.cx-branch=false",
        "--checkmarx.preserve-xml=true",
        f"--cx-xml.data-folder={work}",
        f"--cx-xml.file-name-format={report.name}",
    )
    verify_cxsast(report, str(args.scan_id), args.project, args.team)
    return report


def collect(args: argparse.Namespace, source: Path | None, run_vendor) -> None:
    output = create_capture(source)
    try:
        with tempfile.TemporaryDirectory(dir=output) as work:
            report = export_report(args, source, work, run_vendor)
            report.chmod(0o600)
            saved = report.replace(output / report.name)
        sys.stdout.buffer.write(f"{saved}\n".encode("utf-8"))
        sys.stdout.buffer.flush()
    except BaseException:
        shutil.rmtree(output)
        raise


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--source-root", type=Path, help="absolute source directory to protect")
    products = parser.add_subparsers(dest="product", required=True)
    one = products.add_parser("one", help="export an existing Checkmarx One scan")
    one.add_argument("scan_id")
    one.add_argument("--cx", required=True)
    cxsast = products.add_parser("cxsast", help="retrieve an existing CxSAST scan with CxFlow")
    cxsast.add_argument("scan_id", type=int)
    cxsast.add_argument("--java", required=True)
    cxsast.add_argument("--jar", required=True)
    cxsast.add_argument("--config", required=True)
    cxsast.add_argument("--team", required=True)
    cxsast.add_argument("--project", required=True)
    cxsast.add_argument(
        "--use-env-credentials",
        action="store_true",
        help="use the Checkmarx credentials selected for this profile's server",
    )
    args = parser.parse_args()
    if args.product == "one":
        if not re.fullmatch(
            r"[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}", args.scan_id, re.I | re.ASCII
        ):
            parser.error("Select a Checkmarx One scan UUID in hyphenated form.")
    else:
        if args.scan_id <= 0:
            parser.error("Select an existing positive CxSAST scan ID.")
        if not re.search(r"[^\s/\\]", args.team) or not args.project.strip():
            parser.error("Select a nonblank CxSAST team path and project.")

    pending_signal = None
    defer_termination = False

    def terminate(signum, _frame):
        nonlocal pending_signal
        if pending_signal is None:
            pending_signal = signum
            if not defer_termination:
                raise SystemExit(128 + signum)

    def run_vendor(command, **kwargs):
        nonlocal defer_termination
        child = None
        # Popen can have spawned the child before it returns the handle needed for cleanup.
        defer_termination = True
        try:
            child = subprocess.Popen(command, start_new_session=os.name == "posix", **kwargs)
            defer_termination = False
            if pending_signal is not None:
                raise SystemExit(128 + pending_signal)
            return child.wait()
        except BaseException:
            defer_termination = True
            if child is not None:
                if os.name == "posix":
                    # Kill any surviving credential helper if the process group still exists.
                    with suppress(ProcessLookupError):
                        os.killpg(child.pid, signal.SIGKILL)
                else:
                    child.kill()
                child.wait()
            raise
        finally:
            defer_termination = False

    signals = [signal.SIGTERM]
    if signal.getsignal(signal.SIGINT) != signal.SIG_IGN:
        signals.append(signal.SIGINT)
    if os.name == "posix" and signal.getsignal(signal.SIGHUP) != signal.SIG_IGN:
        signals.append(signal.SIGHUP)
    previous = {number: signal.signal(number, terminate) for number in signals}
    try:
        source = source_root(args.source_root)
        if args.product == "one":
            args.cx = installed_launcher(args.cx, source)
        else:
            args.java = installed_launcher(args.java, source)
            args.jar = installed_file(args.jar, source)
            args.config = installed_file(args.config, source)
            check_cxflow_version(args.jar)
        collect(args, source, run_vendor)
    except (ValueError, OSError, expat.ExpatError) as error:
        print(error, file=sys.stderr)
        return 1
    finally:
        for number, handler in previous.items():
            signal.signal(number, handler)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
```

