From 831fe652571f4f24fe9cb62f2114c1ad0bfc3f05 Mon Sep 17 00:00:00 2001 From: Haksung Jang Date: Sun, 19 Jul 2026 00:30:20 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20P2=20backlog=20=E2=80=94=20split=20VEX?= =?UTF-8?q?=20guide,=20relocate=20cosign=20how-to,=20release=20SBOM,=20onb?= =?UTF-8?q?oarding=20polish?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Seven P2 items from the 2026-07 docs benchmark review: - Split the VEX export/import/UI sections (~230 lines) out of user-guide/vulnerabilities.md into a new user-guide/vex.md — the page mixed reference, how-to and explanation across 21 H2s; the remaining page keeps triage (state machine, EPSS/KEV, upgrade views) and links via a short pointer section. docs-uat ids move with their content (extract green: 32 enrolled docs, 201 steps). - Move reference/sbom-signature-verification.md to ci-integration/ — the content is a five-step how-to, not a reference; inbound links and the moved page's own relative links updated. - Attach a CycloneDX SBOM of the release's own source tree to every GitHub Release (anchore/sbom-action in release.yml's release job, idempotent via --clobber) and document it in releasing.md — an SCA product ships its own SBOM (Dependency-Track convention). - Quickstart step 5: note that on a fresh stack the first real scan can show zero vulnerabilities until the initial Trivy DB download lands (the auto re-match fills findings in; no re-scan). - docs.yml: fetch-depth 0 so showLastUpdateTime (already configured) stamps real per-page dates instead of the deploy date. - Roadmap hygiene in integrations.md and components-and-licenses.md: inline 'on the roadmap' promises become present-tense behaviour plus a pointer; every promise now lives once in the page's Roadmap section (two missing entries added). - Trivy-DB-not-arrived troubleshooting: verified already converged (vulnerability-data.md canonical, scans.md and docker-compose.md are pointers) — no change needed. EN and KO mirrors updated together. Docusaurus EN+KO build green, docs-uat extract green, ko-style S1/S2 0. --- .github/workflows/docs.yml | 4 + .github/workflows/release.yml | 23 ++ .../sbom-signature-verification.md | 12 +- docs-site/docs/contributor-guide/releasing.md | 4 +- docs-site/docs/quickstart.md | 9 + docs-site/docs/reference/faq.md | 4 +- docs-site/docs/reference/glossary.md | 4 +- .../user-guide/components-and-licenses.md | 11 +- docs-site/docs/user-guide/integrations.md | 7 +- docs-site/docs/user-guide/sbom.md | 4 +- docs-site/docs/user-guide/vex.md | 255 ++++++++++++++++++ docs-site/docs/user-guide/vulnerabilities.md | 238 +--------------- .../sbom-signature-verification.md | 12 +- .../current/contributor-guide/releasing.md | 5 +- .../current/quickstart.md | 9 + .../current/reference/faq.md | 4 +- .../current/reference/glossary.md | 4 +- .../user-guide/components-and-licenses.md | 11 +- .../current/user-guide/integrations.md | 7 +- .../current/user-guide/sbom.md | 4 +- .../current/user-guide/vex.md | 241 +++++++++++++++++ .../current/user-guide/vulnerabilities.md | 224 +-------------- docs-site/sidebars.ts | 3 +- 23 files changed, 609 insertions(+), 490 deletions(-) rename docs-site/docs/{reference => ci-integration}/sbom-signature-verification.md (95%) create mode 100644 docs-site/docs/user-guide/vex.md rename docs-site/i18n/ko/docusaurus-plugin-content-docs/current/{reference => ci-integration}/sbom-signature-verification.md (95%) create mode 100644 docs-site/i18n/ko/docusaurus-plugin-content-docs/current/user-guide/vex.md diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index eac7a846..a227fbb5 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -49,6 +49,10 @@ jobs: REDIS_URL: redis://localhost:6379/0 steps: - uses: actions/checkout@v4 + with: + # Full history: `showLastUpdateTime` reads each page's git log — + # a shallow clone would stamp every page with the deploy date. + fetch-depth: 0 # --- Regenerate the OpenAPI spec from the live backend BEFORE the docs # build, so the published Redoc reference always reflects the current diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 02b21866..f53814d3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -345,6 +345,29 @@ jobs: gh release create "$TAG" --draft --title "$TAG" --generate-notes fi + # An SCA product ships its own SBOM (Dependency-Track convention): + # generate a CycloneDX document over the release's checked-out source + # tree and attach it to the (draft) Release. Runs on re-runs too — the + # create step above leaves an existing Release untouched, and --clobber + # makes the asset upload idempotent. + - name: Generate source SBOM (CycloneDX) + uses: anchore/sbom-action@v0 + with: + path: . + format: cyclonedx-json + output-file: trusca-${{ steps.resolve.outputs.version }}.cdx.json + upload-artifact: false + upload-release-assets: false + + - name: Attach SBOM to the Release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ steps.resolve.outputs.tag }} + VERSION: ${{ steps.resolve.outputs.version }} + run: | + set -euo pipefail + gh release upload "$TAG" "trusca-${VERSION}.cdx.json" --clobber + # =========================================================================== # release-gate — prove the PUBLISHED images before revealing the Release # (Phase I / P3-12, steps I2 + I3). Pulls the three multi-arch images at the diff --git a/docs-site/docs/reference/sbom-signature-verification.md b/docs-site/docs/ci-integration/sbom-signature-verification.md similarity index 95% rename from docs-site/docs/reference/sbom-signature-verification.md rename to docs-site/docs/ci-integration/sbom-signature-verification.md index cf84c157..f3e3f581 100644 --- a/docs-site/docs/reference/sbom-signature-verification.md +++ b/docs-site/docs/ci-integration/sbom-signature-verification.md @@ -14,20 +14,20 @@ Every source scan signs its CycloneDX SBOM with [cosign](https://docs.sigstore.d Two readers: - **Verifiers** (sections 1–4) — anyone who consumes a TRUSCA SBOM and wants to prove it is intact and was signed by a known deployment. Assumes a shell and the ability to install a CLI binary. -- **Operators** (section 5) — the person who deploys the portal and owns the signing key. Assumes Linux + Docker Compose proficiency and familiarity with [environment variables](./env-variables.md). +- **Operators** (section 5) — the person who deploys the portal and owns the signing key. Assumes Linux + Docker Compose proficiency and familiarity with [environment variables](../reference/env-variables.md). ::: ## Prerequisites For verification: -1. A TRUSCA account with at least the **Developer** [role](./glossary.md#rbac-roles) on the project's team (the signature endpoints reuse the same access control as the SBOM export — an outsider sees `404`). +1. A TRUSCA account with at least the **Developer** [role](../reference/glossary.md#rbac-roles) on the project's team (the signature endpoints reuse the same access control as the SBOM export — an outsider sees `404`). 2. The project has at least one **succeeded** scan, and signing was configured on the deployment that ran it (see [section 5](#5-operator-key-setup)). A scan that was never signed has no signature artifacts. 3. [cosign](https://docs.sigstore.dev/cosign/installation/) installed on the machine that verifies (see [section 2](#2-install-cosign)). ## 1. Why sign an SBOM? -An [SBOM](./glossary.md#sca-core) tells a consumer *what is inside* a release. A **signature** answers two further questions the SBOM alone cannot: +An [SBOM](../reference/glossary.md#sca-core) tells a consumer *what is inside* a release. A **signature** answers two further questions the SBOM alone cannot: - **Integrity** — were the SBOM bytes altered after the deployment produced them? A signature over the exact bytes detects any tampering. - **Provenance** — *how* was the SBOM produced, and by whom? The [in-toto](https://in-toto.io/) / [SLSA](https://slsa.dev/) provenance attestation records the build platform identity and version. @@ -263,7 +263,7 @@ The attestation stamps a builder identity into the provenance. Set these so a ve | `SLSA_BUILDER_ID` | a vendor-neutral URI | URI naming this build platform in the provenance `builder.id` | | `TRUSTEDOSS_VERSION` | bundled portal version | stamped into `builder.version` and the SBOM-generation context | -See [Environment variables → cosign signing](./env-variables.md) for the full key list and runtime semantics. +See [Environment variables → cosign signing](../reference/env-variables.md) for the full key list and runtime semantics. ## Troubleshooting @@ -297,7 +297,7 @@ The artifact or bundle exceeds the deployment's configured download size cap. Th ## See also - [SBOM](../user-guide/sbom.md) — export the SBOM the signature is over -- [Glossary](./glossary.md) — SBOM, SCA, VEX, and RBAC role definitions -- [Environment variables](./env-variables.md) — the `COSIGN_*` and `SLSA_*` keys +- [Glossary](../reference/glossary.md) — SBOM, SCA, VEX, and RBAC role definitions +- [Environment variables](../reference/env-variables.md) — the `COSIGN_*` and `SLSA_*` keys - [API reference (Redoc)](pathname:///reference/api) — the generated endpoint contract - [Report an issue](https://github.com/trustedoss/trusca/issues/new/choose) — if verification fails unexpectedly diff --git a/docs-site/docs/contributor-guide/releasing.md b/docs-site/docs/contributor-guide/releasing.md index 5f3508de..e8e58133 100644 --- a/docs-site/docs/contributor-guide/releasing.md +++ b/docs-site/docs/contributor-guide/releasing.md @@ -24,7 +24,9 @@ The workflow runs four stages in order, each depending on the previous one: version tags (`X.Y.Z` immutable, `X.Y` movable — never `:latest`). 3. **`release`** — creates the GitHub Release as a **draft**. Notes come from `docs-site/docs/release-notes/X.Y.Z.md` when present, otherwise from GitHub's - auto-generated notes. + auto-generated notes. The job also generates a CycloneDX SBOM of the + release's own source tree (syft) and attaches it as a Release asset + (`trusca-X.Y.Z.cdx.json`) — an SCA product ships its own SBOM. 4. **`release-gate`** — pulls the freshly published `X.Y.Z` images, boots the **production** `docker-compose.yml` (with the small [`docker-compose.smoke.yml`](https://github.com/trustedoss/trusca/blob/main/docker-compose.smoke.yml) diff --git a/docs-site/docs/quickstart.md b/docs-site/docs/quickstart.md index 7d01740c..de2fa5be 100644 --- a/docs-site/docs/quickstart.md +++ b/docs-site/docs/quickstart.md @@ -113,6 +113,15 @@ your own code. Still on the demo stack: **Vulnerabilities** tab shows the open findings — switch it to the **By upgrade** view for the exact version bumps that would clear them. +:::note First boot downloads the vulnerability DB +On a fresh stack the worker downloads the Trivy vulnerability database in the +background (1–3 minutes with internet egress). A scan that finishes before the +download does shows its **components but zero vulnerabilities** — that is the +DB still arriving, not a clean bill of health. No re-scan is needed: the +automatic re-match fills the findings in once the DB lands. See +[Vulnerability data](./admin-guide/vulnerability-data.md). +::: + A private repository needs a credential first — see [Private repositories](./user-guide/projects.md#private-repositories). The full scan reference (container scans, SBOM upload, cancelling, troubleshooting) is diff --git a/docs-site/docs/reference/faq.md b/docs-site/docs/reference/faq.md index 5883a90d..11ebbf26 100644 --- a/docs-site/docs/reference/faq.md +++ b/docs-site/docs/reference/faq.md @@ -116,7 +116,7 @@ Move the finding to an excluded VEX state (`Not affected`, `False positive`, `Su ### Can I generate or import a VEX document? {#vex-export} -Yes — VEX can be exported and re-imported. See [Vulnerabilities](../user-guide/vulnerabilities.md). +Yes — VEX can be exported and re-imported. See [VEX documents](../user-guide/vex.md). ## SBOM and reports {#sbom-reports} @@ -126,7 +126,7 @@ CycloneDX (JSON/XML) and SPDX (JSON/Tag-Value), with optional policy-annotated a ### Can I verify an SBOM was signed by TRUSCA? {#sbom-verify} -Yes — default SBOM exports are signed with cosign and can be verified offline. Note that export profiles are unsigned. See [SBOM signature verification](./sbom-signature-verification.md). +Yes — default SBOM exports are signed with cosign and can be verified offline. Note that export profiles are unsigned. See [SBOM signature verification](../ci-integration/sbom-signature-verification.md). ## Administration and operations {#admin-ops} diff --git a/docs-site/docs/reference/glossary.md b/docs-site/docs/reference/glossary.md index fc7e98f2..318f3041 100644 --- a/docs-site/docs/reference/glossary.md +++ b/docs-site/docs/reference/glossary.md @@ -126,7 +126,7 @@ column. scan's CycloneDX SBOM with cosign (`cosign sign-blob`) so a consumer can verify it with `cosign verify-blob`. Key-based signing is the self-hosted default; keyless (OIDC) is opt-in. See - [Verify SBOM signatures](./sbom-signature-verification.md) and + [Verify SBOM signatures](../ci-integration/sbom-signature-verification.md) and [docs.sigstore.dev/cosign](https://docs.sigstore.dev/cosign/overview/). - **Sigstore / Fulcio / Rekor.** The keyless-signing ecosystem cosign draws on: **Fulcio** issues a short-lived signing certificate bound to @@ -138,7 +138,7 @@ column. [in-toto](https://in-toto.io/) Statement carrying [SLSA](https://slsa.dev/) provenance (builder identity + build context) alongside the SBOM signature. See - [Verify SBOM signatures](./sbom-signature-verification.md#inspect-the-provenance-attestation). + [Verify SBOM signatures](../ci-integration/sbom-signature-verification.md#inspect-the-provenance-attestation). ## License classification diff --git a/docs-site/docs/user-guide/components-and-licenses.md b/docs-site/docs/user-guide/components-and-licenses.md index b913a021..db7d4d7a 100644 --- a/docs-site/docs/user-guide/components-and-licenses.md +++ b/docs-site/docs/user-guide/components-and-licenses.md @@ -226,13 +226,13 @@ the **UI label** column is what appears in tables and badges. | `unknown` | **Unknown** | Surfaced for review; no automatic block. Always needs human review. | License could not be parsed; SPDX ID not matched by the classifier — see [below](#why-so-many-unknown). | :::warning Classification source -The legal-tier classification (`forbidden` / `conditional` / `permissive` / `unknown`) is driven by a built-in SPDX-to-tier catalog. Per-organization rule customization is on the roadmap. Until then, super-admins can override individual entries in-place and restart the worker — an operator-only path. +The legal-tier classification (`forbidden` / `conditional` / `permissive` / `unknown`) is driven by a built-in SPDX-to-tier catalog. Until per-organization rule customization lands (see [Roadmap](#roadmap)), super-admins can override individual entries in-place and restart the worker — an operator-only path. ::: ### Why so many `unknown`? {#why-so-many-unknown} :::info -Classification uses exact-match SPDX IDs. Suffix-less variants (`LGPL-3.0` instead of `LGPL-3.0-or-later`) fall through to `unknown`. If a component shows `unknown` despite a well-known SPDX ID, the source likely emitted a deprecated alias. Fuzzy SPDX normalization is on the roadmap. +Classification uses exact-match SPDX IDs. Suffix-less variants (`LGPL-3.0` instead of `LGPL-3.0-or-later`) fall through to `unknown`. If a component shows `unknown` despite a well-known SPDX ID, the source likely emitted a deprecated alias — see [Roadmap](#roadmap) for the planned fuzzy normalization. ::: A separate, common cause is a manifest that names dependencies without their @@ -333,8 +333,8 @@ kinds yet: - **Trademark restrictions** (Apache-2.0 §6, BSD-4-clause). - **Field-of-use restrictions** (BUSL-1.1). -For these, see the underlying license text via the component drawer; a -richer obligation taxonomy is on the roadmap. +For these, see the underlying license text via the component drawer +(see [Roadmap](#roadmap)). ::: ## Korean license content {#korean-license-content} @@ -387,7 +387,7 @@ The license could not be parsed, or the SPDX ID was not in the classifier's exac ### Classification looks wrong -Classification is driven by the built-in SPDX-to-tier catalog (see [Classification source](#license-classification) above). For a one-off override today, a super-admin can patch the catalog and restart the worker; the per-organization customization path is on the roadmap. If the catalog entry is correct but a detected license disagrees with the declared one, review both findings in the component drawer (see [Declared vs. detected](#declared-vs-detected)). +Classification is driven by the built-in SPDX-to-tier catalog (see [Classification source](#license-classification) above). For a one-off override today, a super-admin can patch the catalog and restart the worker; per-organization customization is tracked on the [Roadmap](#roadmap). If the catalog entry is correct but a detected license disagrees with the declared one, review both findings in the component drawer (see [Declared vs. detected](#declared-vs-detected)). ### Lockfile not detected @@ -403,6 +403,7 @@ Items the manual previously promised that are not in this release; tracked for l - Manual **Override concluded license** action in the drawer (`team_admin`) — planned. - Fuzzy SPDX normalization for suffix-less variants (`LGPL-3.0` → `LGPL-3.0-or-later`) — planned. - Per-organization license-classification rule customization — planned; today classification uses the built-in catalog. +- Richer obligation taxonomy (network-use disclosure, patent-grant termination, trademark restrictions, field-of-use) — planned; today these are read from the license text via the drawer. ## See also diff --git a/docs-site/docs/user-guide/integrations.md b/docs-site/docs/user-guide/integrations.md index 90e8cbce..ef974337 100644 --- a/docs-site/docs/user-guide/integrations.md +++ b/docs-site/docs/user-guide/integrations.md @@ -31,7 +31,7 @@ Open `/integrations` and scroll to the **API keys** section. The list shows ever 2. Fill in the form: - **Name** — free-text reminder of what the key is for (e.g. `github-action-checkout-service`). - - **Scope** — `org`, `team`, or `project`. Lower scopes are stricter; pick the smallest that covers the calls you need to make. The form has plain UUID inputs for `team_id` (required when scope=`team`) and `project_id` (required when scope=`project`); copy the IDs from the corresponding admin pages. A picker UI is on the roadmap. + - **Scope** — `org`, `team`, or `project`. Lower scopes are stricter; pick the smallest that covers the calls you need to make. The form has plain UUID inputs for `team_id` (required when scope=`team`) and `project_id` (required when scope=`project`); copy the IDs from the corresponding admin pages. Who can issue each scope: @@ -44,7 +44,7 @@ Open `/integrations` and scroll to the **API keys** section. The list shows ever 3. Click **Create**. :::caution Keys do not expire in this release -The key-creation form does not yet collect an expiry. Every key issued in this release is valid until you explicitly **Revoke** it. Treat the key like any other long-lived secret — store it in your CI's secret manager, never in source control. An expiry preset is on the roadmap (see below). +The key-creation form does not yet collect an expiry. Every key issued in this release is valid until you explicitly **Revoke** it. Treat the key like any other long-lived secret — store it in your CI's secret manager, never in source control. ::: The portal opens a **one-time reveal modal** with the full key: @@ -126,7 +126,7 @@ URL to register at GitLab: `https:///v1/webhooks/gitlab`. - After registering the webhook in GitHub, push a commit and check the **Webhook deliveries** view in GitHub — successful deliveries return HTTP 200. -- A super-admin can confirm `target_table=api_keys&action=create` and `target_table=webhook_deliveries&action=create` events on `/admin/audit`. Team-scoped audit-log access is on the roadmap (see below). +- A super-admin can confirm `target_table=api_keys&action=create` and `target_table=webhook_deliveries&action=create` events on `/admin/audit`. Today the audit log is super-admin only — see [Roadmap](#roadmap). ## Troubleshooting @@ -141,6 +141,7 @@ URL to register at GitLab: `https:///v1/webhooks/gitlab`. Items the manual previously promised that are not in this release; tracked for later releases. - API-key expiry presets (30 / 90 / 180 / 365 days, custom) — planned; today every issued key is non-expiring until revoked. +- A team / project picker on the key-creation form — planned; today the form takes plain UUID inputs. - **Project Settings → CI/CD** subtab with **Rotate webhook secret** action — planned; today the per-project `webhook_secret` is bootstrapped server-side. - Team-scoped audit log at `/audit` for `team_admin` users — planned; today the audit log is super-admin only at `/admin/audit`. diff --git a/docs-site/docs/user-guide/sbom.md b/docs-site/docs/user-guide/sbom.md index 66318d28..8f3abcd0 100644 --- a/docs-site/docs/user-guide/sbom.md +++ b/docs-site/docs/user-guide/sbom.md @@ -135,7 +135,7 @@ is named distinctly (`sbom--.`) so it is never mistaken for the canonical export. :::caution Profile exports are not signed -The cosign [signature](../reference/sbom-signature-verification.md) covers only +The cosign [signature](../ci-integration/sbom-signature-verification.md) covers only the **default** SBOM — the exact bytes produced at scan time. A policy profile is generated on demand and therefore has **no signature**; `cosign verify-blob` against a profiled export @@ -332,7 +332,7 @@ Items the manual previously promised that are not in this release; tracked for l ## See also -- [Verify SBOM signatures (cosign)](../reference/sbom-signature-verification.md) — prove the SBOM is intact and signed by this deployment +- [Verify SBOM signatures (cosign)](../ci-integration/sbom-signature-verification.md) — prove the SBOM is intact and signed by this deployment - [Components & licenses](./components-and-licenses.md) - [Vulnerabilities](./vulnerabilities.md) - [API overview](../reference/api-overview.md) diff --git a/docs-site/docs/user-guide/vex.md b/docs-site/docs/user-guide/vex.md new file mode 100644 index 00000000..49b38f63 --- /dev/null +++ b/docs-site/docs/user-guide/vex.md @@ -0,0 +1,255 @@ +--- +id: vex +title: VEX documents — export & import +description: Export the project's triage as a standalone OpenVEX or CycloneDX VEX document, import an external one to auto-apply its statements, and drive both from the UI. +sidebar_label: VEX documents +sidebar_position: 5 +--- + +# VEX documents — export & import + +A VEX (Vulnerability Exploitability eXchange) document records *which CVEs +actually affect your product*. Inside the portal that record lives on each +finding as its [VEX status](./vulnerabilities.md#vex-state-machine); this page +covers moving it across the boundary as a **standards document** — exporting +your triage for downstream consumers, and importing someone else's (or a +previously exported) document back onto your findings. + +:::note Audience +Engineers exchanging triage with suppliers or downstream consumers. Export +requires `developer` or higher; import is a bulk-triage action and requires +`team_admin`. +::: + +## Export a VEX document + +Beyond the VEX state embedded in a CycloneDX **SBOM**, the portal can export a +standalone **VEX document** built purely from the project's current finding +triage. A VEX (Vulnerability Exploitability eXchange) document tells downstream +consumers *which CVEs actually affect your product* — so a consumer can suppress +the noise from CVEs you have already analyzed as `not_affected` or `fixed`. + +Two formats are supported: + +| Format | Query value (`format=`) | MIME | Use case | +|---|---|---|---| +| **OpenVEX 0.2.0** | `openvex` | `application/json` | The minimal, vendor-neutral OpenVEX schema. Default. | +| **CycloneDX 1.5 VEX** | `cyclonedx` | `application/json` | A CycloneDX BOM carrying only `vulnerabilities[]` + analysis — pairs with a CycloneDX SBOM. | + +The document is built from the **latest succeeded** scan's findings. A project +with no succeeded scan (or no findings) still exports a valid, empty VEX +document (HTTP 200) so downstream tooling can parse it. + +### Status mapping + +Each internal VEX state maps to the target format's status vocabulary. The +free-text justification you entered during triage is carried verbatim into a +free-text field — it is **never** force-fit onto the OpenVEX `justification` +enum (whose members have precise legal meaning the portal cannot infer from +arbitrary analyst prose). + +| Portal state | OpenVEX `status` | CycloneDX `analysis.state` | +|---|---|---| +| **New** | `under_investigation` | `in_triage` | +| **Analyzing** | `under_investigation` | `in_triage` | +| **Exploitable** | `affected` | `exploitable` | +| **Not affected** | `not_affected` | `not_affected` | +| **False positive** | `not_affected` | `false_positive` | +| **Suppressed** | `not_affected` | `not_affected` | +| **Fixed** | `fixed` | `resolved` | + +The justification text lands in OpenVEX `impact_statement` and in CycloneDX +`analysis.detail`. + +### Byte-stable output + +Like the SBOM export, the VEX export is **byte-stable**: re-exporting the same +scan produces identical bytes, so the document can be signed, cached, and +diffed across releases. Statements are sorted by `(CVE id, purl)`, the document +id is derived deterministically from the scan id, and the timestamp reflects the +scan's persisted completion time (not the moment of export). + +### Download from the API + + +Export the VEX document over the API: + + +```bash +# OpenVEX (default) +curl -sS -L -OJ \ + -H "Authorization: Bearer ${TRUSTEDOSS_API_KEY}" \ + "https://trustedoss.example.com/v1/projects/${PROJECT_ID}/vex?format=openvex" + +# CycloneDX VEX +curl -sS -L -OJ \ + -H "Authorization: Bearer ${TRUSTEDOSS_API_KEY}" \ + "https://trustedoss.example.com/v1/projects/${PROJECT_ID}/vex?format=cyclonedx" +``` + +`format` accepts `openvex` or `cyclonedx`. The file name is +`vex-.`. + +| Status | Meaning | +|---|---| +| `200` | VEX document download. | +| `401` | Not authenticated — supply a valid token. | +| `404` | Project does not exist, or the caller is not a member of its team (existence-hidden, same posture as the SBOM export). | +| `422` | Unknown `format` — use `openvex` or `cyclonedx`. | + +:::note Access +Downloading the VEX document requires `developer` or higher. Cross-team callers +receive `404`, not `403`, so a non-member cannot tell whether the project exists. +::: + +## Import a VEX document (consume) + +The portal can also **import** an external VEX document (OpenVEX or CycloneDX +VEX) and auto-apply its statements to your findings, suppressing triage noise. +This is the inverse of [exporting a VEX document](#export-a-vex-document): +export captures your triage as a standards document; import applies someone +else's (or a previously-exported) document back onto your findings. + +Typical uses: + +- A vendor or upstream maintainer publishes a VEX document saying a CVE is + **not affected** in their package — import it instead of re-triaging by hand. +- You exported a VEX document, edited it in another tool, and want the decisions + back in the portal. +- A CI step generated a VEX document you want to consume on the next sync. + +### Permissions + +VEX import is a **bulk-triage** action — a single upload can transition many +findings — so it requires **`team_admin`** within the project's team (the same +bar as moving a finding *into* `Suppressed`). A `developer` who is a team member +receives `403`; a non-member receives `404` (existence-hidden, same posture as +export). + +### How matching works + +Each VEX statement is matched to a finding by **vulnerability id** (CVE/GHSA/OSV +name) **+ component purl** against your project's **latest succeeded** scan. A +statement that resolves to no finding (the CVE isn't in this scan, or the purl +doesn't match) is **skipped with a reason** — it never errors the whole import. + +### Status mapping (VEX → portal) + +The import reverse-maps each VEX status to a single canonical portal state: + +| OpenVEX `status` | CycloneDX `analysis.state` | Portal state | +|---|---|---| +| `not_affected` | `not_affected` | **Not affected** | +| — | `false_positive` | **False positive** | +| `affected` | `exploitable` | **Exploitable** | +| `fixed` | `resolved` | **Fixed** | +| `under_investigation` | `in_triage` | **Analyzing** | + +`under_investigation` / `in_triage` map to **Analyzing** (not `New`): `New` is +the discovery inbox and nothing transitions *into* it. + +### Legal transitions are preserved + +Import obeys the same [VEX state machine](./vulnerabilities.md#vex-state-machine) as the manual +workflow. Because every verdict routes through `Analyzing`, importing (say) +`not_affected` onto a finding that is still **New** applies the **legal two-step +path** `New → Analyzing → Not affected` automatically, and the audit log records +**both** steps. The justification from the VEX document (`impact_statement` / +`analysis.detail`) is preserved on the finding. + +### Idempotency & round-trip + +Importing the same document twice is safe: a finding already in the target state +is **skipped** (`already_at_target`), not re-written. Exporting your triage and +immediately re-importing it is a **no-op** — the portal's export/import +round-trip is status-stable. + +### Import from the API + + +```bash +curl -sS -X POST \ + -H "Authorization: Bearer ${TRUSTEDOSS_API_KEY}" \ + -F "upload=@vex.openvex.json;type=application/json" \ + "https://trustedoss.example.com/v1/projects/${PROJECT_ID}/vex/import" +``` + +The response is a JSON summary: + +```json +{ + "format": "openvex", + "matched": 12, + "applied": 9, + "skipped": 3, + "errors": [ + { + "vulnerability": "CVE-2024-0001", + "product": "pkg:npm/left-pad@1.0.0", + "reason": "unknown_component", + "detail": "CVE-2024-0001 has no finding on pkg:npm/left-pad@1.0.0 in the latest scan" + } + ] +} +``` + +- `matched` — findings a statement resolved to. +- `applied` — findings whose status actually changed. +- `skipped` — findings/statements deliberately not applied (no-op, unknown + vuln/purl, …). +- `errors[].reason` — one of `unknown_vulnerability`, `unknown_component`, + `ambiguous_match`, `unmapped_status`, `illegal_transition`, + `already_at_target`, `forbidden_transition`, `malformed_statement`. + +| Status | Meaning | +|---|---| +| `200` | Import ran — see the summary (even when 0 applied). | +| `401` | Not authenticated. | +| `403` | Authenticated, member of the team, but not `team_admin`. | +| `404` | Project missing or caller not a team member (existence-hidden). | +| `413` | The uploaded document exceeds the size limit (`VEX_IMPORT_MAX_BYTES`, default 8 MiB). | +| `422` | The document is not valid JSON, or is neither OpenVEX nor CycloneDX VEX. Body is `application/problem+json`. | + +## VEX in the UI + +Everything above is also available without the API, from the **Vulnerabilities tab** +toolbar. + +### Export and import buttons + +- **Export VEX** — two buttons, **OpenVEX** and **CycloneDX VEX**. Click either + to download the project's current triage as a standalone VEX document. The + download goes through your authenticated session (the token never appears in + the URL), the same as the SBOM and PDF report downloads. Export is a read, so + any `developer` (or higher) can use it. +- **Import VEX** — opens a dialog where you choose an OpenVEX or CycloneDX VEX + JSON file and upload it. The format is auto-detected. After the import runs the + dialog shows a summary panel with three counts — **Matched** (findings a + statement resolved to), **Applied** (findings whose status actually changed), + and **Skipped** — plus a per-statement list of skip reasons for anything that + did not apply (unknown CVE/component, illegal transition, already at target, …). + Import is a bulk-triage action: the button is **only enabled for `team_admin`** + (and `super_admin`). A `developer` sees it disabled with a tooltip explaining + the requirement. A `403`, `413`, or `422` from the server is shown inline as a + plain-language message — the dialog never leaves you guessing. + +### Filter: VEX-suppressed only + +The toolbar has a **VEX-suppressed only** checkbox. Turn it on to keep only the +findings on the current page whose status was set by a VEX import +(`analysis_source = vex_import`) — handy for eyeballing exactly what a document you +just imported changed. The toggle is mirrored into the URL (`?vex_suppressed=1`) +so it survives a reload and can be shared as a deep link. Rows set by a VEX import +also carry a small **VEX** badge next to their status, paired with the label (not +color alone) so the provenance is visible at a glance. + +### Provenance badge in the drawer + +Open a finding whose status came from an import and the drawer shows a **VEX +provenance** panel: the consuming document's author, id (`@id` / +`serialNumber`), timestamp, the VEX status the matching statement carried, when +the import ran, and the imported justification. All of these fields come from the +uploaded document and are rendered strictly as **text** — the portal never +interprets them as HTML, so a justification or author containing markup is shown +verbatim and is inert. + diff --git a/docs-site/docs/user-guide/vulnerabilities.md b/docs-site/docs/user-guide/vulnerabilities.md index 7ed1ec48..85f20cde 100644 --- a/docs-site/docs/user-guide/vulnerabilities.md +++ b/docs-site/docs/user-guide/vulnerabilities.md @@ -476,237 +476,14 @@ The PDF response is `application/pdf`; the Excel response is `application/vnd.op Downloading the report requires `developer` or higher. Cross-team callers receive `404`, not `403`, so a non-member cannot tell whether the project exists. ::: -## Export a VEX document +## VEX documents — export & import -Beyond the VEX state embedded in a CycloneDX **SBOM**, the portal can export a -standalone **VEX document** built purely from the project's current finding -triage. A VEX (Vulnerability Exploitability eXchange) document tells downstream -consumers *which CVEs actually affect your product* — so a consumer can suppress -the noise from CVEs you have already analyzed as `not_affected` or `fixed`. - -Two formats are supported: - -| Format | Query value (`format=`) | MIME | Use case | -|---|---|---|---| -| **OpenVEX 0.2.0** | `openvex` | `application/json` | The minimal, vendor-neutral OpenVEX schema. Default. | -| **CycloneDX 1.5 VEX** | `cyclonedx` | `application/json` | A CycloneDX BOM carrying only `vulnerabilities[]` + analysis — pairs with a CycloneDX SBOM. | - -The document is built from the **latest succeeded** scan's findings. A project -with no succeeded scan (or no findings) still exports a valid, empty VEX -document (HTTP 200) so downstream tooling can parse it. - -### Status mapping - -Each internal VEX state maps to the target format's status vocabulary. The -free-text justification you entered during triage is carried verbatim into a -free-text field — it is **never** force-fit onto the OpenVEX `justification` -enum (whose members have precise legal meaning the portal cannot infer from -arbitrary analyst prose). - -| Portal state | OpenVEX `status` | CycloneDX `analysis.state` | -|---|---|---| -| **New** | `under_investigation` | `in_triage` | -| **Analyzing** | `under_investigation` | `in_triage` | -| **Exploitable** | `affected` | `exploitable` | -| **Not affected** | `not_affected` | `not_affected` | -| **False positive** | `not_affected` | `false_positive` | -| **Suppressed** | `not_affected` | `not_affected` | -| **Fixed** | `fixed` | `resolved` | - -The justification text lands in OpenVEX `impact_statement` and in CycloneDX -`analysis.detail`. - -### Byte-stable output - -Like the SBOM export, the VEX export is **byte-stable**: re-exporting the same -scan produces identical bytes, so the document can be signed, cached, and -diffed across releases. Statements are sorted by `(CVE id, purl)`, the document -id is derived deterministically from the scan id, and the timestamp reflects the -scan's persisted completion time (not the moment of export). - -### Download from the API - - -Export the VEX document over the API: - - -```bash -# OpenVEX (default) -curl -sS -L -OJ \ - -H "Authorization: Bearer ${TRUSTEDOSS_API_KEY}" \ - "https://trustedoss.example.com/v1/projects/${PROJECT_ID}/vex?format=openvex" - -# CycloneDX VEX -curl -sS -L -OJ \ - -H "Authorization: Bearer ${TRUSTEDOSS_API_KEY}" \ - "https://trustedoss.example.com/v1/projects/${PROJECT_ID}/vex?format=cyclonedx" -``` - -`format` accepts `openvex` or `cyclonedx`. The file name is -`vex-.`. - -| Status | Meaning | -|---|---| -| `200` | VEX document download. | -| `401` | Not authenticated — supply a valid token. | -| `404` | Project does not exist, or the caller is not a member of its team (existence-hidden, same posture as the SBOM export). | -| `422` | Unknown `format` — use `openvex` or `cyclonedx`. | - -:::note Access -Downloading the VEX document requires `developer` or higher. Cross-team callers -receive `404`, not `403`, so a non-member cannot tell whether the project exists. -::: - -## Import a VEX document (consume) - -The portal can also **import** an external VEX document (OpenVEX or CycloneDX -VEX) and auto-apply its statements to your findings, suppressing triage noise. -This is the inverse of [exporting a VEX document](#export-a-vex-document): -export captures your triage as a standards document; import applies someone -else's (or a previously-exported) document back onto your findings. - -Typical uses: - -- A vendor or upstream maintainer publishes a VEX document saying a CVE is - **not affected** in their package — import it instead of re-triaging by hand. -- You exported a VEX document, edited it in another tool, and want the decisions - back in the portal. -- A CI step generated a VEX document you want to consume on the next sync. - -### Permissions - -VEX import is a **bulk-triage** action — a single upload can transition many -findings — so it requires **`team_admin`** within the project's team (the same -bar as moving a finding *into* `Suppressed`). A `developer` who is a team member -receives `403`; a non-member receives `404` (existence-hidden, same posture as -export). - -### How matching works - -Each VEX statement is matched to a finding by **vulnerability id** (CVE/GHSA/OSV -name) **+ component purl** against your project's **latest succeeded** scan. A -statement that resolves to no finding (the CVE isn't in this scan, or the purl -doesn't match) is **skipped with a reason** — it never errors the whole import. - -### Status mapping (VEX → portal) - -The import reverse-maps each VEX status to a single canonical portal state: - -| OpenVEX `status` | CycloneDX `analysis.state` | Portal state | -|---|---|---| -| `not_affected` | `not_affected` | **Not affected** | -| — | `false_positive` | **False positive** | -| `affected` | `exploitable` | **Exploitable** | -| `fixed` | `resolved` | **Fixed** | -| `under_investigation` | `in_triage` | **Analyzing** | - -`under_investigation` / `in_triage` map to **Analyzing** (not `New`): `New` is -the discovery inbox and nothing transitions *into* it. - -### Legal transitions are preserved - -Import obeys the same [VEX state machine](#vex-state-machine) as the manual -workflow. Because every verdict routes through `Analyzing`, importing (say) -`not_affected` onto a finding that is still **New** applies the **legal two-step -path** `New → Analyzing → Not affected` automatically, and the audit log records -**both** steps. The justification from the VEX document (`impact_statement` / -`analysis.detail`) is preserved on the finding. - -### Idempotency & round-trip - -Importing the same document twice is safe: a finding already in the target state -is **skipped** (`already_at_target`), not re-written. Exporting your triage and -immediately re-importing it is a **no-op** — the portal's export/import -round-trip is status-stable. - -### Import from the API - - -```bash -curl -sS -X POST \ - -H "Authorization: Bearer ${TRUSTEDOSS_API_KEY}" \ - -F "upload=@vex.openvex.json;type=application/json" \ - "https://trustedoss.example.com/v1/projects/${PROJECT_ID}/vex/import" -``` - -The response is a JSON summary: - -```json -{ - "format": "openvex", - "matched": 12, - "applied": 9, - "skipped": 3, - "errors": [ - { - "vulnerability": "CVE-2024-0001", - "product": "pkg:npm/left-pad@1.0.0", - "reason": "unknown_component", - "detail": "CVE-2024-0001 has no finding on pkg:npm/left-pad@1.0.0 in the latest scan" - } - ] -} -``` - -- `matched` — findings a statement resolved to. -- `applied` — findings whose status actually changed. -- `skipped` — findings/statements deliberately not applied (no-op, unknown - vuln/purl, …). -- `errors[].reason` — one of `unknown_vulnerability`, `unknown_component`, - `ambiguous_match`, `unmapped_status`, `illegal_transition`, - `already_at_target`, `forbidden_transition`, `malformed_statement`. - -| Status | Meaning | -|---|---| -| `200` | Import ran — see the summary (even when 0 applied). | -| `401` | Not authenticated. | -| `403` | Authenticated, member of the team, but not `team_admin`. | -| `404` | Project missing or caller not a team member (existence-hidden). | -| `413` | The uploaded document exceeds the size limit (`VEX_IMPORT_MAX_BYTES`, default 8 MiB). | -| `422` | The document is not valid JSON, or is neither OpenVEX nor CycloneDX VEX. Body is `application/problem+json`. | - -## VEX in the UI - -Everything above is also available without the API, from the **Vulnerabilities tab** -toolbar. - -### Export and import buttons - -- **Export VEX** — two buttons, **OpenVEX** and **CycloneDX VEX**. Click either - to download the project's current triage as a standalone VEX document. The - download goes through your authenticated session (the token never appears in - the URL), the same as the SBOM and PDF report downloads. Export is a read, so - any `developer` (or higher) can use it. -- **Import VEX** — opens a dialog where you choose an OpenVEX or CycloneDX VEX - JSON file and upload it. The format is auto-detected. After the import runs the - dialog shows a summary panel with three counts — **Matched** (findings a - statement resolved to), **Applied** (findings whose status actually changed), - and **Skipped** — plus a per-statement list of skip reasons for anything that - did not apply (unknown CVE/component, illegal transition, already at target, …). - Import is a bulk-triage action: the button is **only enabled for `team_admin`** - (and `super_admin`). A `developer` sees it disabled with a tooltip explaining - the requirement. A `403`, `413`, or `422` from the server is shown inline as a - plain-language message — the dialog never leaves you guessing. - -### Filter: VEX-suppressed only - -The toolbar has a **VEX-suppressed only** checkbox. Turn it on to keep only the -findings on the current page whose status was set by a VEX import -(`analysis_source = vex_import`) — handy for eyeballing exactly what a document you -just imported changed. The toggle is mirrored into the URL (`?vex_suppressed=1`) -so it survives a reload and can be shared as a deep link. Rows set by a VEX import -also carry a small **VEX** badge next to their status, paired with the label (not -color alone) so the provenance is visible at a glance. - -### Provenance badge in the drawer - -Open a finding whose status came from an import and the drawer shows a **VEX -provenance** panel: the consuming document's author, id (`@id` / -`serialNumber`), timestamp, the VEX status the matching statement carried, when -the import ran, and the imported justification. All of these fields come from the -uploaded document and are rendered strictly as **text** — the portal never -interprets them as HTML, so a justification or author containing markup is shown -verbatim and is inert. +Your triage can leave the portal as a standards document and come back in: +**export** the project's current finding statuses as an OpenVEX or +CycloneDX VEX document, or **import** an external one to auto-apply its +statements (a vendor's "not affected" verdict, an edited round-trip, a CI +artifact). Both directions, the status mappings, and the UI buttons live on +their own page — see [VEX documents — export & import](./vex.md). ## Re-detection @@ -778,6 +555,7 @@ Items the manual previously promised that are not in this release; tracked for l ## See also +- [VEX documents — export & import](./vex.md) - [Components & licenses](./components-and-licenses.md) - [Approvals](./approvals.md) - [Vulnerability data (Trivy DB)](../admin-guide/vulnerability-data.md) diff --git a/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/reference/sbom-signature-verification.md b/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/ci-integration/sbom-signature-verification.md similarity index 95% rename from docs-site/i18n/ko/docusaurus-plugin-content-docs/current/reference/sbom-signature-verification.md rename to docs-site/i18n/ko/docusaurus-plugin-content-docs/current/ci-integration/sbom-signature-verification.md index 6c598928..755f029e 100644 --- a/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/reference/sbom-signature-verification.md +++ b/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/ci-integration/sbom-signature-verification.md @@ -14,20 +14,20 @@ sidebar_position: 10 두 부류입니다. - **검증자**(1~4절) — TRUSCA SBOM을 소비하며 그것이 변조되지 않았고 알려진 배포에서 서명되었음을 증명하려는 모든 사람. 셸 사용과 CLI 바이너리 설치 능력을 가정합니다. -- **운영자**(5절) — 포털을 배포하고 서명 키를 소유하는 사람. Linux + Docker Compose 숙련과 [환경 변수](./env-variables.md) 이해를 가정합니다. +- **운영자**(5절) — 포털을 배포하고 서명 키를 소유하는 사람. Linux + Docker Compose 숙련과 [환경 변수](../reference/env-variables.md) 이해를 가정합니다. ::: ## 사전 조건 검증을 위해서는: -1. 프로젝트가 속한 팀에서 최소 **Developer** [역할](./glossary.md#rbac-역할)을 가진 TRUSCA 계정. 서명 엔드포인트는 SBOM 내보내기와 동일한 접근 제어를 재사용하므로 외부인은 `404`를 봅니다. +1. 프로젝트가 속한 팀에서 최소 **Developer** [역할](../reference/glossary.md#rbac-역할)을 가진 TRUSCA 계정. 서명 엔드포인트는 SBOM 내보내기와 동일한 접근 제어를 재사용하므로 외부인은 `404`를 봅니다. 2. 프로젝트에 **성공한(succeeded)** 스캔이 하나 이상 있고, 그 스캔을 실행한 배포에 서명이 설정되어 있어야 합니다([5절](#5-운영자-키-설정) 참고). 서명되지 않은 스캔에는 서명 산출물이 없습니다. 3. 검증을 수행하는 머신에 [cosign](https://docs.sigstore.dev/cosign/installation/) 설치([2절](#2-cosign-설치) 참고). ## 1. SBOM에 서명하는 이유 -[SBOM](./glossary.md#sca-핵심)은 릴리스 *안에 무엇이 들어 있는지*를 소비자에게 알려 줍니다. **서명**은 SBOM만으로는 답할 수 없는 두 가지를 추가로 답합니다. +[SBOM](../reference/glossary.md#sca-핵심)은 릴리스 *안에 무엇이 들어 있는지*를 소비자에게 알려 줍니다. **서명**은 SBOM만으로는 답할 수 없는 두 가지를 추가로 답합니다. - **무결성(integrity)** — 배포가 생성한 후 SBOM 바이트가 변경되었는가? 정확한 바이트에 대한 서명은 모든 변조를 탐지합니다. - **출처(provenance)** — SBOM을 *어떻게*, 누가 생성했는가? [in-toto](https://in-toto.io/) / [SLSA](https://slsa.dev/) provenance attestation은 빌드 플랫폼 식별자와 버전을 기록합니다. @@ -263,7 +263,7 @@ attestation은 provenance에 빌더 신원을 새깁니다. 검증자가 provena | `SLSA_BUILDER_ID` | 벤더 중립 URI | provenance `builder.id`에서 이 빌드 플랫폼을 명명하는 URI | | `TRUSTEDOSS_VERSION` | 번들된 포털 버전 | `builder.version`과 SBOM 생성 컨텍스트에 새겨짐 | -전체 키 목록과 런타임 의미는 [환경 변수 → cosign signing](./env-variables.md)을 참고하십시오. +전체 키 목록과 런타임 의미는 [환경 변수 → cosign signing](../reference/env-variables.md)을 참고하십시오. ## 트러블슈팅 @@ -297,7 +297,7 @@ SBOM 바이트가 서명과 맞지 않습니다. 둘 다 **같은** 번들에서 ## 참고 - [SBOM](../user-guide/sbom.md) — 서명 대상 SBOM 내보내기 -- [용어집](./glossary.md) — SBOM·SCA·VEX·RBAC 역할 정의 -- [환경 변수](./env-variables.md) — `COSIGN_*` 및 `SLSA_*` 키 +- [용어집](../reference/glossary.md) — SBOM·SCA·VEX·RBAC 역할 정의 +- [환경 변수](../reference/env-variables.md) — `COSIGN_*` 및 `SLSA_*` 키 - [API 레퍼런스 (Redoc)](pathname:///reference/api) — 생성된 엔드포인트 계약 - [이슈 보고](https://github.com/trustedoss/trusca/issues/new/choose) — 검증이 예기치 않게 실패할 때 diff --git a/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/contributor-guide/releasing.md b/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/contributor-guide/releasing.md index 1158e30c..03193acf 100644 --- a/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/contributor-guide/releasing.md +++ b/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/contributor-guide/releasing.md @@ -24,7 +24,10 @@ TRUSCA 릴리스는 `vX.Y.Z` 형식의 git 태그를 push하면 시작됩니다. 붙입니다(`X.Y.Z`는 불변, `X.Y`는 이동 가능 — `:latest`는 절대 쓰지 않습니다). 3. **`release`** — GitHub Release를 **draft**로 생성합니다. 릴리스 노트는 `docs-site/docs/release-notes/X.Y.Z.md`가 있으면 그것을, 없으면 GitHub가 - 자동 생성한 노트를 씁니다. + 자동 생성한 노트를 씁니다. 이 잡은 릴리스 자신의 소스 트리에 대한 + CycloneDX SBOM도 생성해(syft) Release 자산으로 + 첨부합니다(`trusca-X.Y.Z.cdx.json`) — SCA 제품은 자기 SBOM을 함께 + 내놓습니다. 4. **`release-gate`** — 방금 발행한 `X.Y.Z` 이미지를 pull해서 **프로덕션** `docker-compose.yml`을 기동합니다. 이때 작은 오버레이 [`docker-compose.smoke.yml`](https://github.com/trustedoss/trusca/blob/main/docker-compose.smoke.yml)이 diff --git a/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/quickstart.md b/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/quickstart.md index 36dc1a5a..9f4eebfb 100644 --- a/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/quickstart.md +++ b/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/quickstart.md @@ -108,6 +108,15 @@ finding·의무사항 묶음이 약 10초 안에 생성됩니다. (`--demo-only` 탭에 미해결 결과가 나타납니다 — **업그레이드별** 보기로 전환하면 결과를 해소하는 정확한 버전 인상 목록을 볼 수 있습니다. +:::note 첫 부팅 시 취약점 데이터베이스를 내려받습니다 +새 스택에서는 워커가 Trivy 취약점 데이터베이스를 백그라운드로 내려받습니다 +(인터넷 연결 시 1~3분). 다운로드가 끝나기 전에 완료된 스캔은 **컴포넌트는 +보이지만 취약점이 0건**으로 나옵니다 — 데이터베이스가 아직 도착하지 않은 +것이지, 깨끗하다는 판정이 아닙니다. 재스캔은 필요 없습니다. 데이터베이스가 +도착하면 자동 재매칭이 결과를 채웁니다. +[취약점 데이터](./admin-guide/vulnerability-data.md) 참고. +::: + 사설 저장소는 자격 증명을 먼저 등록해야 합니다 — [사설 저장소](./user-guide/projects.md#사설-저장소) 참고. 스캔 전체 레퍼런스(컨테이너 스캔, SBOM 업로드, 취소, 문제 해결)는 diff --git a/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/reference/faq.md b/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/reference/faq.md index b135e67a..2536325a 100644 --- a/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/reference/faq.md +++ b/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/reference/faq.md @@ -116,7 +116,7 @@ finding을 제외 VEX 상태(`Not affected`, `False positive`, `Suppressed`, `Fi ### VEX 문서를 생성하거나 가져올 수 있나요? {#vex-export} -가능합니다. VEX는 내보내고 다시 가져올 수 있습니다. [취약점](../user-guide/vulnerabilities.md)을 참고하세요. +가능합니다. VEX는 내보내고 다시 가져올 수 있습니다. [VEX 문서](../user-guide/vex.md)를 참고하세요. ## SBOM과 보고서 {#sbom-reports} @@ -126,7 +126,7 @@ CycloneDX(JSON/XML)와 SPDX(JSON/Tag-Value)를 지원하며, 선택적으로 정 ### SBOM이 TRUSCA로 서명되었는지 검증할 수 있나요? {#sbom-verify} -가능합니다. 기본 SBOM 수출은 cosign으로 서명되며 오프라인에서 검증할 수 있습니다. 수출 프로파일은 서명되지 않습니다. [SBOM 서명 검증](./sbom-signature-verification.md)을 참고하세요. +가능합니다. 기본 SBOM 수출은 cosign으로 서명되며 오프라인에서 검증할 수 있습니다. 수출 프로파일은 서명되지 않습니다. [SBOM 서명 검증](../ci-integration/sbom-signature-verification.md)을 참고하세요. ## 운영·관리 {#admin-ops} diff --git a/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/reference/glossary.md b/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/reference/glossary.md index c09645e4..a64d3975 100644 --- a/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/reference/glossary.md +++ b/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/reference/glossary.md @@ -121,7 +121,7 @@ sidebar_position: 4 CycloneDX SBOM을 cosign(`cosign sign-blob`)으로 서명하여 소비자가 `cosign verify-blob`으로 검증할 수 있게 합니다. 자체 호스팅에서는 key-based 서명이 기본값이고 keyless(OIDC)는 옵트인입니다. - [SBOM 서명 검증](./sbom-signature-verification.md)과 + [SBOM 서명 검증](../ci-integration/sbom-signature-verification.md)과 [docs.sigstore.dev/cosign](https://docs.sigstore.dev/cosign/overview/) 참고. - **Sigstore / Fulcio / Rekor.** cosign이 keyless 서명에 사용하는 생태계: **Fulcio**는 OIDC 신원에 바인딩된 단기 서명 인증서를 @@ -132,7 +132,7 @@ sidebar_position: 4 생성되었는지에 대한 서명된 명세. TRUSCA는 SBOM 서명과 함께 [SLSA](https://slsa.dev/) provenance(빌더 신원 + 빌드 컨텍스트)를 담은 [in-toto](https://in-toto.io/) Statement를 생성합니다. - [SBOM 서명 검증](./sbom-signature-verification.md#provenance-attestation-확인) 참고. + [SBOM 서명 검증](../ci-integration/sbom-signature-verification.md#provenance-attestation-확인) 참고. ## 라이선스 분류 diff --git a/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/user-guide/components-and-licenses.md b/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/user-guide/components-and-licenses.md index 5537b404..5af174c1 100644 --- a/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/user-guide/components-and-licenses.md +++ b/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/user-guide/components-and-licenses.md @@ -223,13 +223,13 @@ Components 탭에는 **표 / 그래프** 토글이 있습니다(좌측 상단). | `unknown` | **Unknown** | 검토 대상으로 노출; 자동 차단 없음. 항상 사람의 검토 필요. | 라이선스 파싱 실패 또는 분류기 매핑에 없는 SPDX ID — [아래](#why-so-many-unknown) 참고. | :::warning 분류 출처 -법적 단계 분류(`forbidden` / `conditional` / `permissive` / `unknown`)는 내장 SPDX → 단계 카탈로그로 결정됩니다. 조직별 룰 커스터마이징은 로드맵 항목입니다. 오늘 일회성 오버라이드가 필요하면 super-admin 이 카탈로그 항목을 패치하고 워커를 재시작하는 operator 전용 경로를 사용하세요. +법적 단계 분류(`forbidden` / `conditional` / `permissive` / `unknown`)는 내장 SPDX → 단계 카탈로그로 결정됩니다. 조직별 룰 커스터마이징이 나오기 전까지는([로드맵](#로드맵) 참고) super-admin 이 카탈로그 항목을 패치하고 워커를 재시작하는 operator 전용 경로를 사용하세요. ::: ### `unknown` 이 왜 이렇게 많은가? {#why-so-many-unknown} :::info -분류는 정확 일치(exact-match) SPDX ID 를 사용합니다. 접미사 없는 변형(`LGPL-3.0-or-later` 대신 `LGPL-3.0`)은 `unknown` 으로 떨어집니다. 잘 알려진 SPDX ID 인데도 `unknown` 으로 표시된다면 출처가 deprecated alias 를 발신했을 가능성이 높습니다. fuzzy SPDX 정규화는 로드맵 항목입니다. +분류는 정확 일치(exact-match) SPDX ID 를 사용합니다. 접미사 없는 변형(`LGPL-3.0-or-later` 대신 `LGPL-3.0`)은 `unknown` 으로 떨어집니다. 잘 알려진 SPDX ID 인데도 `unknown` 으로 표시된다면 출처가 deprecated alias 를 발신했을 가능성이 높습니다 — 계획된 fuzzy 정규화는 [로드맵](#로드맵) 참고. ::: 또 다른 흔한 원인은 의존성 이름만 있고 라이선스가 없는 매니페스트입니다 — @@ -329,8 +329,8 @@ scancode 처럼 이 단계도 **best-effort** 입니다: SCANOSS 가 꺼져 있 - **상표권 제한**(Apache-2.0 §6, BSD-4-clause). - **사용 분야 제한**(BUSL-1.1). -이 항목은 컴포넌트 드로어에서 라이선스 원문을 통해 확인하세요. 더 -풍부한 의무사항 분류 체계는 로드맵 항목입니다. +이 항목은 컴포넌트 드로어에서 라이선스 원문을 통해 +확인하세요([로드맵](#로드맵) 참고). ::: ## 한국어 라이선스 콘텐츠 {#korean-license-content} @@ -382,7 +382,7 @@ UI에서 표현 위에 마우스를 올리면 각 컴포넌트 라이선스의 S ### 분류가 잘못된 것 같음 -분류는 내장 SPDX → 단계 카탈로그로 결정됩니다([위의 분류 출처](#라이선스-분류) 참고). 오늘 일회성 오버라이드가 필요하면 super-admin 이 카탈로그 항목을 패치하고 워커를 재시작하세요; 조직별 커스터마이징 경로는 로드맵 항목입니다. 카탈로그 항목이 맞는데 detected 라이선스가 declared 와 불일치하면, 컴포넌트 드로어에서 두 결과를 모두 검토하세요([declared vs. detected](#declared-vs-detected) 참고). +분류는 내장 SPDX → 단계 카탈로그로 결정됩니다([위의 분류 출처](#라이선스-분류) 참고). 오늘 일회성 오버라이드가 필요하면 super-admin 이 카탈로그 항목을 패치하고 워커를 재시작하세요. 조직별 커스터마이징은 [로드맵](#로드맵)에서 추적합니다. 카탈로그 항목이 맞는데 detected 라이선스가 declared 와 불일치하면, 컴포넌트 드로어에서 두 결과를 모두 검토하세요([declared vs. detected](#declared-vs-detected) 참고). ### 락파일이 탐지되지 않음 @@ -398,6 +398,7 @@ UI에서 표현 위에 마우스를 올리면 각 컴포넌트 라이선스의 S - 드로어의 수동 **Concluded 라이선스 오버라이드** 동작(`team_admin`) — 예정. - 접미사 없는 변형(`LGPL-3.0` → `LGPL-3.0-or-later`)을 위한 fuzzy SPDX 정규화 — 예정. - 조직별 라이선스 분류 룰 커스터마이징 — 예정. 오늘은 내장 카탈로그가 분류를 결정합니다. +- 더 풍부한 의무사항 분류 체계(네트워크 사용 고지, 특허 부여 종료, 상표권 제한, 사용 분야 제한) — 예정. 오늘은 드로어의 라이선스 원문으로 확인합니다. ## 함께 보기 diff --git a/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/user-guide/integrations.md b/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/user-guide/integrations.md index 4dc618ca..5475e222 100644 --- a/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/user-guide/integrations.md +++ b/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/user-guide/integrations.md @@ -31,7 +31,7 @@ sidebar_position: 9 2. 폼을 채웁니다. - **Name** — Key 용도를 떠올리게 하는 자유 텍스트(예: `github-action-checkout-service`). - - **Scope** — `org`, `team`, `project`. 낮은 scope가 더 엄격합니다. 필요한 호출을 커버하는 가장 작은 scope를 선택하세요. 폼에는 `team_id`(scope=`team`일 때 필수)와 `project_id`(scope=`project`일 때 필수)를 위한 평문 UUID 입력란이 있습니다. 해당 admin 페이지에서 ID를 복사해 넣으세요. 선택기 UI는 로드맵 항목입니다. + - **Scope** — `org`, `team`, `project`. 낮은 scope가 더 엄격합니다. 필요한 호출을 커버하는 가장 작은 scope를 선택하세요. 폼에는 `team_id`(scope=`team`일 때 필수)와 `project_id`(scope=`project`일 때 필수)를 위한 평문 UUID 입력란이 있습니다. 해당 admin 페이지에서 ID를 복사해 넣으세요. 각 scope 발급 권한: @@ -44,7 +44,7 @@ sidebar_position: 9 3. **Create**를 클릭합니다. :::caution 현재 릴리스에서는 Key가 만료되지 않음 -Key 생성 폼이 아직 만료를 받지 않습니다. 현재 릴리스에서 발급된 모든 Key는 명시적으로 **Revoke** 할 때까지 유효합니다. 다른 장기 시크릿과 동일하게 취급하세요 — CI 시크릿 매니저에 보관하고 절대 소스 컨트롤에 두지 마세요. 만료 프리셋은 로드맵 항목입니다(아래 참고). +Key 생성 폼이 아직 만료를 받지 않습니다. 현재 릴리스에서 발급된 모든 Key는 명시적으로 **Revoke** 할 때까지 유효합니다. 다른 장기 시크릿과 동일하게 취급하세요 — CI 시크릿 매니저에 보관하고 절대 소스 컨트롤에 두지 마세요. ::: 포털은 전체 Key가 담긴 **1회 노출 모달**을 엽니다. @@ -126,7 +126,7 @@ GitLab에 등록할 URL — `https:///v1/webhooks/gitlab`. - GitHub에 Webhook 등록 후 커밋을 푸시하고 GitHub의 **Webhook deliveries** 뷰에서 HTTP 200 성공 전송을 확인하세요. -- super-admin이 `/admin/audit`에서 `target_table=api_keys&action=create`와 `target_table=webhook_deliveries&action=create` 이벤트를 확인할 수 있습니다. team-범위 감사 로그는 로드맵 항목입니다(아래 참고). +- super-admin이 `/admin/audit`에서 `target_table=api_keys&action=create`와 `target_table=webhook_deliveries&action=create` 이벤트를 확인할 수 있습니다. 현재 감사 로그는 super-admin 전용입니다 — [로드맵](#로드맵) 참고. ## 트러블슈팅 @@ -141,6 +141,7 @@ GitLab에 등록할 URL — `https:///v1/webhooks/gitlab`. 매뉴얼이 이전에 약속했으나 v0.10.0에 포함되지 않은 항목. - API Key 만료 프리셋(30 / 90 / 180 / 365일, 커스텀) — 예정. 현재 발급된 모든 Key는 폐기 전까지 만료되지 않습니다. +- Key 생성 폼의 팀/프로젝트 선택기 — 예정. 현재 폼은 평문 UUID 입력을 받습니다. - **Project Settings → CI/CD** 서브탭과 **Rotate webhook secret** 동작 — 예정. 현재 프로젝트별 `webhook_secret`은 서버 측에서 부트스트랩됩니다. - `team_admin`을 위한 팀 범위 감사 로그(`/audit`) — 예정. 현재 감사 로그는 super-admin 전용 (`/admin/audit`). diff --git a/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/user-guide/sbom.md b/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/user-guide/sbom.md index 9c81bfa9..2f5c7dfe 100644 --- a/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/user-guide/sbom.md +++ b/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/user-guide/sbom.md @@ -133,7 +133,7 @@ curl -sS -L -OJ \ 혼동되지 않습니다. :::caution 프로파일 내보내기에는 서명이 붙지 않습니다 -cosign [서명](../reference/sbom-signature-verification.md)은 **기본** SBOM — +cosign [서명](../ci-integration/sbom-signature-verification.md)은 **기본** SBOM — 스캔 시점에 생성된 정확한 바이트 — 에만 적용됩니다. 정책 프로파일은 요청 시점에 생성되므로 **서명이 없으며**, 프로파일 내보내기에 대해 `cosign verify-blob`을 실행하면 실패합니다. 검증 가능한 서명 산출물이 필요하면 기본 내보내기를 쓰고, @@ -317,7 +317,7 @@ SBOM·NOTICE 본문은 구조적 세부(컴포넌트 이름·버전)를 노출 ## 함께 보기 -- [SBOM 서명 검증 (cosign)](../reference/sbom-signature-verification.md) — SBOM이 온전하며 이 배포에서 서명되었음을 증명 +- [SBOM 서명 검증 (cosign)](../ci-integration/sbom-signature-verification.md) — SBOM이 온전하며 이 배포에서 서명되었음을 증명 - [컴포넌트·라이선스](./components-and-licenses.md) - [취약점](./vulnerabilities.md) - [API 개요](../reference/api-overview.md) diff --git a/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/user-guide/vex.md b/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/user-guide/vex.md new file mode 100644 index 00000000..57794617 --- /dev/null +++ b/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/user-guide/vex.md @@ -0,0 +1,241 @@ +--- +id: vex +title: VEX 문서 — 내보내기와 가져오기 +description: 프로젝트의 트리아지를 독립 OpenVEX·CycloneDX VEX 문서로 내보내고, 외부 문서를 가져와 statement를 자동 적용하며, 두 방향 모두 UI에서 수행합니다. +sidebar_label: VEX 문서 +sidebar_position: 5 +--- + +# VEX 문서 — 내보내기와 가져오기 + +VEX(Vulnerability Exploitability eXchange) 문서는 *어떤 CVE가 실제로 제품에 +영향을 주는지*를 기록합니다. 포털 안에서 그 기록은 각 결과의 +[VEX 상태](./vulnerabilities.md#vex-상태-머신)로 존재합니다. 이 페이지는 그 +기록을 **표준 문서**로 경계 밖과 주고받는 방법을 다룹니다 — 트리아지를 +다운스트림 소비자를 위해 내보내고, 다른 사람의(또는 이전에 내보낸) 문서를 +가져와 결과에 다시 적용합니다. + +:::note 대상 독자 +공급자·다운스트림과 트리아지를 교환하는 엔지니어. 내보내기는 `developer` +이상, 가져오기는 일괄 트리아지 동작이라 `team_admin` 이 필요합니다. +::: + +## VEX 문서 내보내기 + +CycloneDX **SBOM**에 포함되는 VEX 상태와 별개로, 포털은 프로젝트의 현재 결과 +분류(triage)만으로 구성한 **독립 VEX 문서**를 내보낼 수 있습니다. VEX(Vulnerability +Exploitability eXchange) 문서는 다운스트림 소비자에게 *어떤 CVE가 실제로 제품에 +영향을 주는지*를 알려줍니다 — 따라서 소비자는 이미 `not_affected`나 `fixed`로 +분석한 CVE의 노이즈를 억제할 수 있습니다. + +두 가지 포맷을 지원합니다: + +| 포맷 | 쿼리 값 (`format=`) | MIME | 용도 | +|---|---|---|---| +| **OpenVEX 0.2.0** | `openvex` | `application/json` | 최소한의 벤더 중립 OpenVEX 스키마. 기본값. | +| **CycloneDX 1.5 VEX** | `cyclonedx` | `application/json` | `vulnerabilities[]` + 분석만 담은 CycloneDX BOM — CycloneDX SBOM과 짝을 이룹니다. | + +문서는 **가장 최근에 성공한(latest succeeded)** 스캔의 결과로부터 만들어집니다. +성공한 스캔이 없거나(또는 결과가 없는) 프로젝트라도 다운스트림 도구가 파싱할 수 +있도록 유효한 빈 VEX 문서(HTTP 200)를 내보냅니다. + +### 상태 매핑 + +각 내부 VEX 상태는 대상 포맷의 상태 어휘로 매핑됩니다. 분류 중 입력한 자유 텍스트 +justification은 자유 텍스트 필드에 그대로 전달됩니다 — OpenVEX `justification` +enum(임의의 분석가 서술로는 추론할 수 없는 정확한 법적 의미를 가짐)에 억지로 +끼워 맞추지 **않습니다**. + +| 포털 상태 | OpenVEX `status` | CycloneDX `analysis.state` | +|---|---|---| +| **New** | `under_investigation` | `in_triage` | +| **Analyzing** | `under_investigation` | `in_triage` | +| **Exploitable** | `affected` | `exploitable` | +| **Not affected** | `not_affected` | `not_affected` | +| **False positive** | `not_affected` | `false_positive` | +| **Suppressed** | `not_affected` | `not_affected` | +| **Fixed** | `fixed` | `resolved` | + +justification 텍스트는 OpenVEX `impact_statement`와 CycloneDX `analysis.detail`에 +들어갑니다. + +### 바이트 안정 출력 + +SBOM 내보내기와 마찬가지로 VEX 내보내기는 **바이트 안정(byte-stable)** 합니다. +동일한 스캔을 다시 내보내면 동일한 바이트가 생성되므로 문서를 서명·캐싱하고 +릴리스 간에 diff할 수 있습니다. statement는 `(CVE id, purl)` 순으로 정렬되고, +문서 id는 스캔 id에서 결정적으로 파생되며, 타임스탬프는 내보낸 순간이 아니라 +스캔의 영속화된 완료 시각을 반영합니다. + +### API에서 다운로드 + + +VEX 문서를 API로 내보냅니다: + + +```bash +# OpenVEX (기본값) +curl -sS -L -OJ \ + -H "Authorization: Bearer ${TRUSTEDOSS_API_KEY}" \ + "https://trustedoss.example.com/v1/projects/${PROJECT_ID}/vex?format=openvex" + +# CycloneDX VEX +curl -sS -L -OJ \ + -H "Authorization: Bearer ${TRUSTEDOSS_API_KEY}" \ + "https://trustedoss.example.com/v1/projects/${PROJECT_ID}/vex?format=cyclonedx" +``` + +`format`은 `openvex` 또는 `cyclonedx`를 받습니다. 파일명은 +`vex-.`. + +| 상태 | 의미 | +|---|---| +| `200` | VEX 문서 다운로드. | +| `401` | 미인증 — 유효한 토큰을 제공하세요. | +| `404` | 프로젝트가 없거나, 호출자가 해당 팀의 멤버가 아님(existence-hide, SBOM 내보내기와 동일한 정책). | +| `422` | 알 수 없는 `format` — `openvex` 또는 `cyclonedx`를 사용하세요. | + +:::note 접근 권한 +VEX 문서 다운로드는 `developer` 이상이 필요합니다. 크로스팀 호출자는 `403`이 아닌 +`404`를 받으므로 비멤버는 프로젝트의 존재 여부를 알 수 없습니다. +::: + +## VEX 문서 import(소비) + +포털은 외부 VEX 문서(OpenVEX 또는 CycloneDX VEX)를 **import**하여 그 statement를 +결과에 자동 적용함으로써 분류 노이즈를 억제할 수 있습니다. 이는 +[VEX 문서 내보내기](#vex-문서-내보내기)의 역방향입니다 — export는 분류 결과를 +표준 문서로 내보내고, import는 (다른 사람의 또는 이전에 내보낸) 문서를 다시 +결과에 적용합니다. + +대표적 사용 사례: + +- 벤더나 상위 메인테이너가 특정 CVE를 자신의 패키지에서 **해당 없음(not affected)** + 으로 선언한 VEX 문서를 발행 → 수동 재분류 대신 import. +- VEX 문서를 export해 다른 도구에서 편집한 뒤 결정을 포털로 되돌리기. +- CI 단계에서 생성한 VEX 문서를 다음 동기화 때 소비하기. + +### 권한 + +VEX import는 **대량 분류** 행위로(업로드 한 번이 다수의 결과를 전이시킬 수 있음) +프로젝트 팀 내 **`team_admin`**이 필요합니다(결과를 `Suppressed`로 전이할 때와 +동일한 기준). 팀 멤버인 `developer`는 `403`을, 비멤버는 `404`(존재 은닉, export와 +동일한 태도)를 받습니다. + +### 매칭 방식 + +각 VEX statement는 **취약점 id**(CVE/GHSA/OSV 이름) **+ 컴포넌트 purl**로 +프로젝트의 **최신 성공 스캔**의 결과와 매칭됩니다. 매칭되는 결과가 없는 +statement(해당 스캔에 CVE가 없거나 purl이 일치하지 않음)는 **사유와 함께 skip**되며, +전체 import를 실패시키지 않습니다. + +### 상태 매핑 (VEX → 포털) + +import는 각 VEX 상태를 하나의 정규 포털 상태로 역매핑합니다: + +| OpenVEX `status` | CycloneDX `analysis.state` | 포털 상태 | +|---|---|---| +| `not_affected` | `not_affected` | **Not affected** | +| — | `false_positive` | **False positive** | +| `affected` | `exploitable` | **Exploitable** | +| `fixed` | `resolved` | **Fixed** | +| `under_investigation` | `in_triage` | **Analyzing** | + +`under_investigation` / `in_triage`는 `New`가 아닌 **Analyzing**으로 매핑됩니다 — +`New`는 탐지 인박스 상태이며 어떤 것도 `New`로는 전이되지 않습니다. + +### 합법 전이 보존 + +import는 수동 워크플로우와 동일한 [VEX 상태 머신](./vulnerabilities.md#vex-상태-머신)을 따릅니다. +모든 판정이 `Analyzing`을 거치므로, 아직 **New**인 결과에 `not_affected`를 +import하면 **합법 2단계 경로** `New → Analyzing → Not affected`가 자동 적용되고, +감사 로그에 **두 단계 모두** 기록됩니다. VEX 문서의 사유(`impact_statement` / +`analysis.detail`)는 결과에 보존됩니다. + +### 멱등성 & 왕복 + +같은 문서를 두 번 import해도 안전합니다: 이미 목표 상태인 결과는 다시 쓰지 않고 +**skip**(`already_at_target`)합니다. 분류 결과를 export한 뒤 곧바로 다시 import하면 +**no-op**입니다 — 포털의 export/import 왕복은 상태가 안정적입니다. + +### API에서 import + + +```bash +curl -sS -X POST \ + -H "Authorization: Bearer ${TRUSTEDOSS_API_KEY}" \ + -F "upload=@vex.openvex.json;type=application/json" \ + "https://trustedoss.example.com/v1/projects/${PROJECT_ID}/vex/import" +``` + +응답은 JSON 요약입니다: + +```json +{ + "format": "openvex", + "matched": 12, + "applied": 9, + "skipped": 3, + "errors": [ + { + "vulnerability": "CVE-2024-0001", + "product": "pkg:npm/left-pad@1.0.0", + "reason": "unknown_component", + "detail": "CVE-2024-0001 has no finding on pkg:npm/left-pad@1.0.0 in the latest scan" + } + ] +} +``` + +- `matched` — statement가 매칭한 결과 수. +- `applied` — 상태가 실제로 변경된 결과 수. +- `skipped` — 의도적으로 적용하지 않은 결과/statement(no-op, 미지 vuln/purl 등). +- `errors[].reason` — `unknown_vulnerability`, `unknown_component`, + `ambiguous_match`, `unmapped_status`, `illegal_transition`, + `already_at_target`, `forbidden_transition`, `malformed_statement` 중 하나. + +| 상태 | 의미 | +|---|---| +| `200` | import 실행됨 — 요약 참고(적용 0건이어도 200). | +| `401` | 미인증. | +| `403` | 인증됨, 팀 멤버이나 `team_admin`이 아님. | +| `404` | 프로젝트 없음 또는 호출자가 팀 멤버 아님(존재 은닉). | +| `413` | 업로드 문서가 크기 제한(`VEX_IMPORT_MAX_BYTES`, 기본 8 MiB)을 초과. | +| `422` | 문서가 유효한 JSON이 아니거나 OpenVEX/CycloneDX VEX가 아님. 본문은 `application/problem+json`. | + +## UI에서의 VEX + +위의 모든 작업은 API 없이도 **취약점 탭** 툴바에서 수행할 수 있습니다. + +### 내보내기·가져오기 버튼 + +- **VEX 내보내기** — **OpenVEX**, **CycloneDX VEX** 두 개의 버튼이 있습니다. 둘 중 + 하나를 클릭하면 프로젝트의 현재 트리아지를 독립 VEX 문서로 다운로드합니다. + 다운로드는 SBOM·PDF 보고서와 동일하게 인증 세션을 통해 이루어지며(토큰이 URL에 + 노출되지 않음), 읽기 작업이므로 `developer` 이상이면 누구나 사용할 수 있습니다. +- **VEX 가져오기** — OpenVEX 또는 CycloneDX VEX JSON 파일을 선택해 업로드하는 + 다이얼로그를 엽니다. 형식은 자동으로 감지됩니다. 가져오기가 끝나면 다이얼로그에 + **일치**(구문이 매칭한 취약점), **적용**(상태가 실제로 변경된 취약점), + **건너뜀** 세 가지 카운트와, 적용되지 않은 항목에 대한 구문별 건너뜀 사유 목록 + (알 수 없는 CVE/컴포넌트, 허용되지 않는 전이, 이미 대상 상태 등)이 표시됩니다. + 가져오기는 대량 트리아지 작업이므로 버튼은 **`team_admin`(및 `super_admin`)에게만 + 활성화**됩니다. `developer`에게는 권한 안내 툴팁과 함께 비활성화되어 표시됩니다. + 서버의 `403`·`413`·`422` 응답은 평이한 문장 메시지로 인라인 표시됩니다. + +### 필터: VEX로 억제된 항목만 + +툴바에는 **VEX로 억제된 항목만** 체크박스가 있습니다. 켜면 현재 페이지에서 상태가 +VEX 가져오기로 설정된(`analysis_source = vex_import`) 취약점만 남습니다 — 방금 +가져온 문서가 무엇을 바꿨는지 확인할 때 유용합니다. 이 토글은 URL +(`?vex_suppressed=1`)에 반영되어 새로고침해도 유지되고 딥링크로 공유할 수 있습니다. +VEX 가져오기로 설정된 행에는 상태 옆에 작은 **VEX** 배지가 색상이 아닌 레이블과 함께 +표시되어 출처를 한눈에 알 수 있습니다. + +### 드로어의 출처 배지 + +가져오기로 상태가 설정된 취약점을 열면 드로어에 **VEX 출처** 패널이 표시됩니다: +소비된 문서의 작성자, ID(`@id` / `serialNumber`), 타임스탬프, 매칭된 구문이 담은 VEX +상태, 가져온 시각, 그리고 가져온 근거가 나옵니다. 이 필드들은 모두 업로드된 문서에서 +오며 엄격히 **텍스트**로만 렌더링됩니다 — 포털은 이를 HTML로 해석하지 않으므로, +마크업이 포함된 근거나 작성자도 그대로 표시되며 동작하지 않습니다(inert). + diff --git a/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/user-guide/vulnerabilities.md b/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/user-guide/vulnerabilities.md index fd397a3a..fd62a769 100644 --- a/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/user-guide/vulnerabilities.md +++ b/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/user-guide/vulnerabilities.md @@ -471,224 +471,13 @@ PDF 응답은 `application/pdf`, Excel 응답은 `application/vnd.openxmlformats 보고서 다운로드는 `developer` 이상이 필요합니다. 크로스팀 호출자는 `403`이 아닌 `404`를 받으므로 비멤버는 프로젝트의 존재 여부를 알 수 없습니다. ::: -## VEX 문서 내보내기 +## VEX 문서 — 내보내기와 가져오기 -CycloneDX **SBOM**에 포함되는 VEX 상태와 별개로, 포털은 프로젝트의 현재 결과 -분류(triage)만으로 구성한 **독립 VEX 문서**를 내보낼 수 있습니다. VEX(Vulnerability -Exploitability eXchange) 문서는 다운스트림 소비자에게 *어떤 CVE가 실제로 제품에 -영향을 주는지*를 알려줍니다 — 따라서 소비자는 이미 `not_affected`나 `fixed`로 -분석한 CVE의 노이즈를 억제할 수 있습니다. - -두 가지 포맷을 지원합니다: - -| 포맷 | 쿼리 값 (`format=`) | MIME | 용도 | -|---|---|---|---| -| **OpenVEX 0.2.0** | `openvex` | `application/json` | 최소한의 벤더 중립 OpenVEX 스키마. 기본값. | -| **CycloneDX 1.5 VEX** | `cyclonedx` | `application/json` | `vulnerabilities[]` + 분석만 담은 CycloneDX BOM — CycloneDX SBOM과 짝을 이룹니다. | - -문서는 **가장 최근에 성공한(latest succeeded)** 스캔의 결과로부터 만들어집니다. -성공한 스캔이 없거나(또는 결과가 없는) 프로젝트라도 다운스트림 도구가 파싱할 수 -있도록 유효한 빈 VEX 문서(HTTP 200)를 내보냅니다. - -### 상태 매핑 - -각 내부 VEX 상태는 대상 포맷의 상태 어휘로 매핑됩니다. 분류 중 입력한 자유 텍스트 -justification은 자유 텍스트 필드에 그대로 전달됩니다 — OpenVEX `justification` -enum(임의의 분석가 서술로는 추론할 수 없는 정확한 법적 의미를 가짐)에 억지로 -끼워 맞추지 **않습니다**. - -| 포털 상태 | OpenVEX `status` | CycloneDX `analysis.state` | -|---|---|---| -| **New** | `under_investigation` | `in_triage` | -| **Analyzing** | `under_investigation` | `in_triage` | -| **Exploitable** | `affected` | `exploitable` | -| **Not affected** | `not_affected` | `not_affected` | -| **False positive** | `not_affected` | `false_positive` | -| **Suppressed** | `not_affected` | `not_affected` | -| **Fixed** | `fixed` | `resolved` | - -justification 텍스트는 OpenVEX `impact_statement`와 CycloneDX `analysis.detail`에 -들어갑니다. - -### 바이트 안정 출력 - -SBOM 내보내기와 마찬가지로 VEX 내보내기는 **바이트 안정(byte-stable)** 합니다. -동일한 스캔을 다시 내보내면 동일한 바이트가 생성되므로 문서를 서명·캐싱하고 -릴리스 간에 diff할 수 있습니다. statement는 `(CVE id, purl)` 순으로 정렬되고, -문서 id는 스캔 id에서 결정적으로 파생되며, 타임스탬프는 내보낸 순간이 아니라 -스캔의 영속화된 완료 시각을 반영합니다. - -### API에서 다운로드 - - -VEX 문서를 API로 내보냅니다: - - -```bash -# OpenVEX (기본값) -curl -sS -L -OJ \ - -H "Authorization: Bearer ${TRUSTEDOSS_API_KEY}" \ - "https://trustedoss.example.com/v1/projects/${PROJECT_ID}/vex?format=openvex" - -# CycloneDX VEX -curl -sS -L -OJ \ - -H "Authorization: Bearer ${TRUSTEDOSS_API_KEY}" \ - "https://trustedoss.example.com/v1/projects/${PROJECT_ID}/vex?format=cyclonedx" -``` - -`format`은 `openvex` 또는 `cyclonedx`를 받습니다. 파일명은 -`vex-.`. - -| 상태 | 의미 | -|---|---| -| `200` | VEX 문서 다운로드. | -| `401` | 미인증 — 유효한 토큰을 제공하세요. | -| `404` | 프로젝트가 없거나, 호출자가 해당 팀의 멤버가 아님(existence-hide, SBOM 내보내기와 동일한 정책). | -| `422` | 알 수 없는 `format` — `openvex` 또는 `cyclonedx`를 사용하세요. | - -:::note 접근 권한 -VEX 문서 다운로드는 `developer` 이상이 필요합니다. 크로스팀 호출자는 `403`이 아닌 -`404`를 받으므로 비멤버는 프로젝트의 존재 여부를 알 수 없습니다. -::: - -## VEX 문서 import(소비) - -포털은 외부 VEX 문서(OpenVEX 또는 CycloneDX VEX)를 **import**하여 그 statement를 -결과에 자동 적용함으로써 분류 노이즈를 억제할 수 있습니다. 이는 -[VEX 문서 내보내기](#vex-문서-내보내기)의 역방향입니다 — export는 분류 결과를 -표준 문서로 내보내고, import는 (다른 사람의 또는 이전에 내보낸) 문서를 다시 -결과에 적용합니다. - -대표적 사용 사례: - -- 벤더나 상위 메인테이너가 특정 CVE를 자신의 패키지에서 **해당 없음(not affected)** - 으로 선언한 VEX 문서를 발행 → 수동 재분류 대신 import. -- VEX 문서를 export해 다른 도구에서 편집한 뒤 결정을 포털로 되돌리기. -- CI 단계에서 생성한 VEX 문서를 다음 동기화 때 소비하기. - -### 권한 - -VEX import는 **대량 분류** 행위로(업로드 한 번이 다수의 결과를 전이시킬 수 있음) -프로젝트 팀 내 **`team_admin`**이 필요합니다(결과를 `Suppressed`로 전이할 때와 -동일한 기준). 팀 멤버인 `developer`는 `403`을, 비멤버는 `404`(존재 은닉, export와 -동일한 태도)를 받습니다. - -### 매칭 방식 - -각 VEX statement는 **취약점 id**(CVE/GHSA/OSV 이름) **+ 컴포넌트 purl**로 -프로젝트의 **최신 성공 스캔**의 결과와 매칭됩니다. 매칭되는 결과가 없는 -statement(해당 스캔에 CVE가 없거나 purl이 일치하지 않음)는 **사유와 함께 skip**되며, -전체 import를 실패시키지 않습니다. - -### 상태 매핑 (VEX → 포털) - -import는 각 VEX 상태를 하나의 정규 포털 상태로 역매핑합니다: - -| OpenVEX `status` | CycloneDX `analysis.state` | 포털 상태 | -|---|---|---| -| `not_affected` | `not_affected` | **Not affected** | -| — | `false_positive` | **False positive** | -| `affected` | `exploitable` | **Exploitable** | -| `fixed` | `resolved` | **Fixed** | -| `under_investigation` | `in_triage` | **Analyzing** | - -`under_investigation` / `in_triage`는 `New`가 아닌 **Analyzing**으로 매핑됩니다 — -`New`는 탐지 인박스 상태이며 어떤 것도 `New`로는 전이되지 않습니다. - -### 합법 전이 보존 - -import는 수동 워크플로우와 동일한 [VEX 상태 머신](#vex-상태-머신)을 따릅니다. -모든 판정이 `Analyzing`을 거치므로, 아직 **New**인 결과에 `not_affected`를 -import하면 **합법 2단계 경로** `New → Analyzing → Not affected`가 자동 적용되고, -감사 로그에 **두 단계 모두** 기록됩니다. VEX 문서의 사유(`impact_statement` / -`analysis.detail`)는 결과에 보존됩니다. - -### 멱등성 & 왕복 - -같은 문서를 두 번 import해도 안전합니다: 이미 목표 상태인 결과는 다시 쓰지 않고 -**skip**(`already_at_target`)합니다. 분류 결과를 export한 뒤 곧바로 다시 import하면 -**no-op**입니다 — 포털의 export/import 왕복은 상태가 안정적입니다. - -### API에서 import - - -```bash -curl -sS -X POST \ - -H "Authorization: Bearer ${TRUSTEDOSS_API_KEY}" \ - -F "upload=@vex.openvex.json;type=application/json" \ - "https://trustedoss.example.com/v1/projects/${PROJECT_ID}/vex/import" -``` - -응답은 JSON 요약입니다: - -```json -{ - "format": "openvex", - "matched": 12, - "applied": 9, - "skipped": 3, - "errors": [ - { - "vulnerability": "CVE-2024-0001", - "product": "pkg:npm/left-pad@1.0.0", - "reason": "unknown_component", - "detail": "CVE-2024-0001 has no finding on pkg:npm/left-pad@1.0.0 in the latest scan" - } - ] -} -``` - -- `matched` — statement가 매칭한 결과 수. -- `applied` — 상태가 실제로 변경된 결과 수. -- `skipped` — 의도적으로 적용하지 않은 결과/statement(no-op, 미지 vuln/purl 등). -- `errors[].reason` — `unknown_vulnerability`, `unknown_component`, - `ambiguous_match`, `unmapped_status`, `illegal_transition`, - `already_at_target`, `forbidden_transition`, `malformed_statement` 중 하나. - -| 상태 | 의미 | -|---|---| -| `200` | import 실행됨 — 요약 참고(적용 0건이어도 200). | -| `401` | 미인증. | -| `403` | 인증됨, 팀 멤버이나 `team_admin`이 아님. | -| `404` | 프로젝트 없음 또는 호출자가 팀 멤버 아님(존재 은닉). | -| `413` | 업로드 문서가 크기 제한(`VEX_IMPORT_MAX_BYTES`, 기본 8 MiB)을 초과. | -| `422` | 문서가 유효한 JSON이 아니거나 OpenVEX/CycloneDX VEX가 아님. 본문은 `application/problem+json`. | - -## UI에서의 VEX - -위의 모든 작업은 API 없이도 **취약점 탭** 툴바에서 수행할 수 있습니다. - -### 내보내기·가져오기 버튼 - -- **VEX 내보내기** — **OpenVEX**, **CycloneDX VEX** 두 개의 버튼이 있습니다. 둘 중 - 하나를 클릭하면 프로젝트의 현재 트리아지를 독립 VEX 문서로 다운로드합니다. - 다운로드는 SBOM·PDF 보고서와 동일하게 인증 세션을 통해 이루어지며(토큰이 URL에 - 노출되지 않음), 읽기 작업이므로 `developer` 이상이면 누구나 사용할 수 있습니다. -- **VEX 가져오기** — OpenVEX 또는 CycloneDX VEX JSON 파일을 선택해 업로드하는 - 다이얼로그를 엽니다. 형식은 자동으로 감지됩니다. 가져오기가 끝나면 다이얼로그에 - **일치**(구문이 매칭한 취약점), **적용**(상태가 실제로 변경된 취약점), - **건너뜀** 세 가지 카운트와, 적용되지 않은 항목에 대한 구문별 건너뜀 사유 목록 - (알 수 없는 CVE/컴포넌트, 허용되지 않는 전이, 이미 대상 상태 등)이 표시됩니다. - 가져오기는 대량 트리아지 작업이므로 버튼은 **`team_admin`(및 `super_admin`)에게만 - 활성화**됩니다. `developer`에게는 권한 안내 툴팁과 함께 비활성화되어 표시됩니다. - 서버의 `403`·`413`·`422` 응답은 평이한 문장 메시지로 인라인 표시됩니다. - -### 필터: VEX로 억제된 항목만 - -툴바에는 **VEX로 억제된 항목만** 체크박스가 있습니다. 켜면 현재 페이지에서 상태가 -VEX 가져오기로 설정된(`analysis_source = vex_import`) 취약점만 남습니다 — 방금 -가져온 문서가 무엇을 바꿨는지 확인할 때 유용합니다. 이 토글은 URL -(`?vex_suppressed=1`)에 반영되어 새로고침해도 유지되고 딥링크로 공유할 수 있습니다. -VEX 가져오기로 설정된 행에는 상태 옆에 작은 **VEX** 배지가 색상이 아닌 레이블과 함께 -표시되어 출처를 한눈에 알 수 있습니다. - -### 드로어의 출처 배지 - -가져오기로 상태가 설정된 취약점을 열면 드로어에 **VEX 출처** 패널이 표시됩니다: -소비된 문서의 작성자, ID(`@id` / `serialNumber`), 타임스탬프, 매칭된 구문이 담은 VEX -상태, 가져온 시각, 그리고 가져온 근거가 나옵니다. 이 필드들은 모두 업로드된 문서에서 -오며 엄격히 **텍스트**로만 렌더링됩니다 — 포털은 이를 HTML로 해석하지 않으므로, -마크업이 포함된 근거나 작성자도 그대로 표시되며 동작하지 않습니다(inert). +트리아지는 표준 문서로 포털을 나갔다가 돌아올 수 있습니다. 프로젝트의 현재 +결과 상태를 OpenVEX 또는 CycloneDX VEX 문서로 **내보내거나**, 외부 +문서(공급업체의 "해당 없음" 판정, 편집한 왕복 문서, CI 산출물)를 **가져와** +statement를 자동 적용합니다. 두 방향과 상태 매핑, UI 버튼은 별도 페이지에서 +다룹니다 — [VEX 문서 — 내보내기와 가져오기](./vex.md) 참고. ## 재탐지 @@ -761,6 +550,7 @@ PDF는 요청 시점에 weasyprint로 렌더링됩니다. `500`(본문은 `appli ## 함께 보기 +- [VEX 문서 — 내보내기와 가져오기](./vex.md) - [컴포넌트·라이선스](./components-and-licenses.md) - [승인](./approvals.md) - [취약점 데이터 (Trivy DB)](../admin-guide/vulnerability-data.md) diff --git a/docs-site/sidebars.ts b/docs-site/sidebars.ts index a39aa8e6..86d07327 100644 --- a/docs-site/sidebars.ts +++ b/docs-site/sidebars.ts @@ -31,6 +31,7 @@ const sidebars: SidebarsConfig = { "user-guide/scans", "user-guide/components-and-licenses", "user-guide/vulnerabilities", + "user-guide/vex", "user-guide/triage", "user-guide/sbom", "user-guide/ai-sbom-conformance", @@ -90,6 +91,7 @@ const sidebars: SidebarsConfig = { "ci-integration/jenkins", "ci-integration/webhooks", "ci-integration/sbom-upload", + "ci-integration/sbom-signature-verification", ], }, { @@ -108,7 +110,6 @@ const sidebars: SidebarsConfig = { "reference/obligation-catalog", "reference/remediation-dry-run", "reference/remediation-pull-request", - "reference/sbom-signature-verification", "reference/design-system", ], },