Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions docs/scanner-imports/semgrep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Import Semgrep Code findings

Prefer native **Semgrep Code/SAST CLI or CI JSON** for this reader. It preserves
the scanner's original severity and source evidence. Self-contained SARIF 2.1.0
is a compatibility path. Both formats may be plain or gzip-compressed.

This reader does not accept Platform API, Guardian or MCP response envelopes,
or a general Supply Chain or Secrets feed. Those products and acquisition
contracts require separate mappings; installing a plugin does not change the
formats accepted here.

To produce JSON locally, run the following from the source repository root with
your selected SAST rules. This runs a new scan; it does not retrieve earlier
findings from Semgrep AppSec Platform.

```sh
semgrep scan --config /path/to/sast-rules.yaml --json-output semgrep.json .
codex-security publish findings semgrep.json --to local \
--provider semgrep --source-key ORGANIZATION/PROJECT/BRANCH \
--source-repository-url https://github.com/example/project \
--repository . --dry-run --format json
```

Existing CI jobs can add `--json-output semgrep.json` to their SAST scan and pass
that artifact to the publisher. With authenticated `semgrep ci`, select `--code`;
its output follows the job's policy and PR/baseline scope and may contain only new
blocking findings. It is not a complete inventory of Platform findings.
[CLI output and scope reference](https://docs.semgrep.dev/cli-reference).

Review the findings, source repository and exclusions, then repeat the command
without `--dry-run` and confirm the import, or add `--yes` after review. Local
imports require no Cloud login and store unassessed evidence in the workbench
SQLite database. Read it with `codex-security findings imported . --format json`.
Repeated imports with the same source identity update the same records; omitted
records remain stored. See the movement behavior under Evidence and identity.
They do not create a completed scan or automatically assess or patch findings.

For Cloud, use the same report and source selection with a Cloud destination:

```sh
codex-security publish findings semgrep.json --to cloud \
--provider semgrep --source-key ORGANIZATION/PROJECT/BRANCH \
--source-repository-url https://github.com/example/project \
--repository https://github.com/example/project --dry-run --format json
```

Keep the same source key for one organization, project and branch across runs.
`--source-repository-url` declares where the report came from; the importer checks
it against configured local Git remotes or the Cloud destination. Cloud requires
an authorized repository and support for the `semgrep` provider. Local SARIF
imports also check every original `versionControlProvenance.repositoryUri` on
accepted findings. Unknown Git hosts keep case-sensitive repository paths.

## Existing Platform findings

Semgrep's official
[Guardian Codex plugin](https://docs.semgrep.dev/semgrep-guardian/ide-setup/codex)
can select and inspect existing findings. Its output is not accepted by this
reader. A Platform importer needs a mapping qualified against the hosted tools'
actual responses, repository/ref filters and pagination using an authorized test
tenant. The separate local `semgrep mcp` server also exposes `semgrep_findings`,
but its current tool
returns a limited list without continuation metadata. It cannot establish a
complete export. [Official MCP implementation](https://github.com/semgrep/semgrep/blob/fe209763a4a2ccfcdf6434e6c332edc24a2f586e/cli/src/semgrep/mcp/server.py).

For an automated acquisition path, Semgrep's
[findings API](https://docs.semgrep.dev/semgrep-appsec-platform/semgrep-api) supports
pagination with a Web API token. Its Platform finding objects
need their own mapping into the shared publisher; they are not CLI `results`
objects. Do not rename an API/MCP response to `semgrep.json` or reconstruct a scan
report from a summary. No manual website export is required for either path.

## Evidence and identity

JSON preserves original severity, rule, CWE IDs, source range, snippets when
available and ignored state. SARIF retains its selected result and rule, including
trace and fix evidence. Its Critical and High ratings both become diagnostic
`error`, displayed here as High; prefer JSON when that distinction matters.
Original evidence stays in `source_data`, and source state stays separate from a
Codex assessment.

Both local and Cloud file imports currently apply the shared Cloud evidence
bounds, including a maximum JSON depth of 20. Deeply nested native taint traces
can exceed that bound and are excluded with a reason in the preview. The reader
preserves accepted source evidence without flattening or truncating traces.

Keep the supplied JSON `extra.fingerprint` or SARIF `matchBasedId/v1`
fingerprint. If a later report supplies the same fingerprint after the code
moves, importing it under the same repository and source key updates that record
with the new range; the raw fingerprint stays in `source_data`.

Community Edition may omit the fingerprint or emit `requires login`. The fallback
identity uses the rule, literal path and range. Moving such a finding creates a
second record, and the earlier record remains stored. The importer does not
infer that they are the same finding, merge them or delete the earlier record.
Read back both with `codex-security findings imported . --format json` before
selecting evidence for assessment. A current checkout does not establish the
report's revision or branch.

The adapter accepts SAST only. Select SAST rules or a Code-only CI scan, since
SARIF may omit enough product metadata to distinguish Secrets. Identifiable
Supply Chain and Secrets records are excluded. Reports with scan errors,
unsuccessful SARIF invocations or scan-error notifications are rejected. The
preview reports invalid findings and duplicate source identities before import.

SARIF supports direct artifact URIs and Semgrep's native `%SRCROOT%` marker without
a base definition. Native paths retain literal percent characters; direct URIs
are decoded once. Indexed artifacts and unresolved bases remain in original
evidence without mapped repository locations. CE records with these references
are excluded because they lack a usable identity. JSON scan-wide path inventories
and timing tables are omitted from individual findings. SARIF artifact inventories
are omitted when they are unrelated to the selected finding; required inventories
retain their original entries and indexes. Empty external-property reference
containers are accepted, while actual external file references are rejected.

SDK callers pass `{ sourceRepositoryUrl }` to `normalizeSemgrepFindings`,
`normalizeSemgrepSarif`, or `readSemgrepFindings`. When passing that normalized
output directly to `prepareNormalizedLocalImport`, supply the original SARIF
repository references in the third argument's `sourceRepositories`; the file
importer forwards them automatically. For an independently known GitHub custom
host, use `{ url, platform: "github" }` for its references. The Semgrep file reader
accepts native reports, not normalized JSON/JSONL exports.
See the [native output fields](https://docs.semgrep.dev/semgrep-appsec-platform/json-and-sarif).
14 changes: 13 additions & 1 deletion plugins/codex-security/skills/publish-findings/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: publish-findings
description: Import selected Snyk Open Source findings or Wiz package vulnerabilities, SAST, repository secrets, and IaC findings locally or to Codex Security Cloud after previewing the destination and evidence. Prefers native JSON; also supports Snyk plugin SCA bundles and Open Source SARIF, Wiz normalized JSONL and repository package-vulnerability CSV, and gzip; does not import workload secrets or cloud configuration, run a scan, assess findings, or synchronize triage decisions.
description: Import selected Snyk Open Source findings, Semgrep Code CLI/CI reports, or Wiz package vulnerabilities, SAST, repository secrets, and IaC findings locally or to Codex Security Cloud after previewing the destination and evidence. Prefers native JSON; also supports Snyk plugin SCA bundles, Snyk Open Source and Semgrep Code SARIF, Wiz normalized JSONL and repository package-vulnerability CSV, and gzip; does not import workload secrets or cloud configuration, run a scan, assess findings, or synchronize triage decisions.
---

# Publish external findings
Expand Down Expand Up @@ -55,6 +55,18 @@ Use one format consistently per repository, branch and scan target, with a stabl

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](#local-imports) for preview, confirmation and readback or [Cloud publication](#review-and-publish-to-cloud) for upload authorization. All three Snyk formats support either destination.

## 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. The reader does not accept Platform API, Guardian or MCP response envelopes, or a general Supply Chain or Secrets feed. 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 existing findings, the official [Semgrep Guardian Codex plugin](https://docs.semgrep.dev/semgrep-guardian/ide-setup/codex) can help select and inspect them. Importing its output needs a separate qualified response mapping and complete pagination for the selected repository/ref. The local `semgrep_findings` MCP tool and Platform REST API also use contracts distinct from CLI reports. Do not reshape a summary into a scan report or claim that a limited tool response is a complete export.

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.

## Review and publish to Cloud

When publishing to staging or another Cloud deployment, set `CODEX_SECURITY_CLOUD_BASE_URL` to its API root before both preview and upload. If the Cloud page or user supplies its app URL, also set `CODEX_SECURITY_CLOUD_WEB_URL` to that full URL so the command can return a repository link. The UI setting does not change API routing or authentication. Use that deployment's login and an isolated state directory for tests. Verify `cloudApiUrl` and any `cloudUrl` in the preview against the intended deployment; the former is the API endpoint and the latter is the UI link. Saved retries are separated by deployment. The native scan setting `CODEX_SECURITY_CLOUD_PUBLISH_URL` does not route vendor imports. Never fall back to production when staging is unavailable.
Expand Down
2 changes: 2 additions & 0 deletions sdk/typescript/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,8 @@ reports carry this platform in their repository inventory. If you export only th
normalized finding rows to JSONL, supply that metadata separately; the original
finding evidence does not include the surrounding inventory.

See [Semgrep Code imports](https://github.com/openai/codex-security/blob/main/docs/scanner-imports/semgrep.md) for native CLI/CI JSON, SARIF compatibility, and local or Cloud imports. Platform API and Guardian/MCP responses need a separate mapping.

## Findings storage and deduplication

Saved-scan deduplication uses local SQLite directly:
Expand Down
1 change: 1 addition & 0 deletions sdk/typescript/scripts/package-dist-files.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const distModules = [
"external-findings-read",
"scanner-import",
"snyk-findings",
"semgrep-findings",
"external-findings-publish",
"external-import-contract",
"external-import-models",
Expand Down
13 changes: 9 additions & 4 deletions sdk/typescript/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3531,15 +3531,15 @@ export async function main(
});
publication.command("findings", {
description:
"Preview and import selected Wiz or Snyk findings locally or to Cloud.",
hint: "For Snyk Open Source SCA vulnerabilities only, prefer native snyk test JSON or an official plugin snyk-findings-export/v1 bundle; Open Source SARIF 2.1.0 is also supported. Snyk licenses, Code, Container, IaC and platform backlogs are unsupported. For Wiz, prefer a native package vulnerability JSON report or named SAST/repository-secret/IaC JSON collections with repository metadata. Normalized JSONL and repository package-vulnerability CSV (SCA only, GitHub repositories) are also supported. Gzip is supported for each format; Wiz CSV does not cover SAST or cloud posture. Other Wiz CSV profiles, scan events, workload secrets, and cloud configuration exports are not supported. --to local stores unassessed evidence for a local repository path without Cloud login. --full-output requires --format json or --format jsonl.\nExample: codex-security publish findings selected-wiz.json --to local --repository ./project --provider wiz --source-key TENANT_ID/vulnerability-finding --dry-run --format json",
"Preview and import selected Wiz, Snyk, or Semgrep Code findings locally or to Cloud.",
hint: "For Snyk Open Source SCA vulnerabilities only, prefer native snyk test JSON or an official plugin snyk-findings-export/v1 bundle; Open Source SARIF 2.1.0 is also supported. Snyk licenses, Code, Container, IaC and platform backlogs are unsupported. For Semgrep Code, supply native JSON or SARIF and --source-repository-url. For Wiz, prefer a native package vulnerability JSON report or named SAST/repository-secret/IaC JSON collections with repository metadata. Normalized JSONL and repository package-vulnerability CSV (SCA only, GitHub repositories) are also supported. Gzip is supported for each format; Wiz CSV does not cover SAST or cloud posture. Other Wiz CSV profiles, scan events, workload secrets, and cloud configuration exports are not supported. --to local stores unassessed evidence for a local repository path without Cloud login. --full-output requires --format json or --format jsonl.\nExample: codex-security publish findings selected-wiz.json --to local --repository ./project --provider wiz --source-key TENANT_ID/vulnerability-finding --dry-run --format json",
destructive: true,
mcp: false,
args: z.object({
file: z
.string()
.describe(
"Snyk native test JSON, plugin SCA bundle or Open Source SARIF; native Wiz JSON, normalized JSONL, or repository package-vulnerability CSV.",
"Snyk native test JSON, plugin SCA bundle or Open Source SARIF; Semgrep Code JSON/SARIF; native Wiz JSON, normalized JSONL, or repository package-vulnerability CSV.",
),
}),
options: z.object({
Expand All @@ -3548,8 +3548,13 @@ export async function main(
"Local repository path for --to local; authorized repository ID or URL for --to cloud.",
),
provider: z
.enum(["wiz", "snyk"])
.enum(["wiz", "snyk", "semgrep"])
.describe("Vendor that produced these findings."),
sourceRepositoryUrl: optionValue("--source-repository-url")
.optional()
.describe(
"Required for Semgrep: source repository URL, checked against the Cloud destination or configured local Git remotes.",
),
sourceKey: optionValue("--source-key").describe(
"Stable vendor tenant and finding namespace; not a project selection filter.",
),
Expand Down
32 changes: 31 additions & 1 deletion sdk/typescript/src/external-findings-import.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import type { ExternalPublicationOptions } from "./external-findings-publish.js";
import {
cloudRepositoryUrlKey,
repositoryUrlKey,
} from "./external-import-contract.js";
import { readExternalFindings } from "./external-findings-read.js";
import {
prepareNormalizedLocalImport,
Expand All @@ -17,11 +21,37 @@ export async function prepareLocalExternalImport(
() => readExternalFindings(path, options),
dependencies.signal,
);
let sourceRepositories = parsed.sourceRepositories ?? [];
if (options.provider === "semgrep") {
// The Cloud reader allows custom GitHub casing. Local provenance must also
// agree with the caller's source URL when the checkout has no remote.
const source = options.sourceRepositoryUrl!;
const knownGitHub = dependencies.sourceRepositories?.some(
({ url, platform }) =>
platform === "github" &&
cloudRepositoryUrlKey(url) === cloudRepositoryUrlKey(source),
);
const key = knownGitHub ? cloudRepositoryUrlKey : repositoryUrlKey;
Comment thread
mldangelo-oai marked this conversation as resolved.
for (const reference of sourceRepositories) {
if (key(reference.url) !== key(source))
throw new Error(
"SARIF source repository does not match the declared source repository URL.",
);
}
if (knownGitHub)
sourceRepositories = [
{ url: source, platform: "github" },
...sourceRepositories.map((reference) => ({
...reference,
platform: "github" as const,
})),
];
}
return prepareNormalizedLocalImport(parsed.findings, options, {
...dependencies,
sourceRepositories: [
...(dependencies.sourceRepositories ?? []),
...(parsed.sourceRepositories ?? []),
...sourceRepositories,
],
});
}
1 change: 1 addition & 0 deletions sdk/typescript/src/external-findings-publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export interface NormalizedExternalPublicationOptions {
/** File readers accept the vendor formats installed in this SDK. */
export interface ExternalPublicationOptions extends NormalizedExternalPublicationOptions {
provider: ExternalFindingsReaderOptions["provider"];
sourceRepositoryUrl?: string;
}

/** Optional transport, account, cancellation, and progress hooks. */
Expand Down
Loading
Loading