Register Local Patches

Register rebuilt Maven artifacts so Zero-Touch Remediation can serve local patches at the original coordinate without changing builds.

Some organizations backport security fixes themselves instead of consuming a clearing-house rebuild. When those patched Maven artifacts already sit in an Artifactory local repository, Zero-Touch Remediation can serve them at the original coordinate: no pom.xml change, Catalog-backed candidate selection, scan reconciliation, and an audit trail.

Registration is a REST API request. After a patch is accepted, JFrog Catalog can return it as a candidate. Artifactory serves it only when a covered Maven virtual repository includes that local repository as a member.

Local patches are Maven only. npm lockfile healing does not consume locally registered patches.

Prerequisites

  • Platform access with Manage Data permission on each artifact you register.
  • A patched Maven artifact uploaded to a local Artifactory repository.
  • That local repository is indexed in Xray.
  • That local repository is a member of at least one Maven virtual repository covered by an active Zero-Touch Remediation automation. Membership is what allows Artifactory to serve the patched bytes at the original coordinate. The registration request does not name the virtual repository.
  • JFrog Catalog access. Zero-Touch Remediation translates your CycloneDX declaration into Catalog ingest so the patch becomes a selectable candidate.

How Registration Fits the Remediation Flow

  1. You build the patched artifact and upload it to a local repository.
  2. You register the artifact with POST /xray/api/v1/ztr/patches, declaring which original coordinate it replaces and which vulnerabilities it remediates.
  3. Zero-Touch Remediation validates the request and ingests the declaration into Catalog.
  4. On a later download of the original coordinate from a covered virtual repository that includes the local repository as a member, Xray can select the local patch using Least Vulnerable. When scores are equal, a local patch ranks above a vendor rebuild.
  5. Scan results reflect Catalog data for the served patched version.

How to register local patches

  1. Upload the patched Maven artifact to an Xray-indexed local repository that is a member of a covered Maven virtual repository:

    jf rt upload <PATCHED_FILE> \
      <LOCAL_REPO>/<ARTIFACT_PATH>

    Where:

    • <PATCHED_FILE>: The patched Maven artifact file
    • <LOCAL_REPO>: The key of the Xray-indexed local repository
    • <ARTIFACT_PATH>: The path of the artifact inside the local repository

    For example:

    jf rt upload spring-core-5.3.39-local.1.jar \
      patches-local/org/springframework/spring-core/5.3.39-local.1/spring-core-5.3.39-local.1.jar

    The artifact is stored in the local repository.

  2. Record the artifact path relative to the local repository and the artifact SHA-256. The SHA-256 must match both the stored file and the CycloneDX component hash. You have the path and SHA-256 required for the registration request.

  3. Send a POST request to /xray/api/v1/ztr/patches. One request can register up to 100 patches. The serialized body must not exceed 1 MiB.

    Authentication uses a bearer token or HTTP basic authentication. Inspect every item in results. HTTP 200 means the batch was processed, not that every patch was stored.

    curl -X POST \
      -H "Authorization: Bearer <TOKEN>" \
      -H "Content-Type: application/json" \
      "https://<JFROG_PLATFORM_URL>/xray/api/v1/ztr/patches" \
      -d @local-patches.json

    Where:

    • <TOKEN>: Your JFrog access token
    • <JFROG_PLATFORM_URL>: The URL of your JFrog Platform

    For example:

    curl -X POST \
      -H "Authorization: Bearer <TOKEN>" \
      -H "Content-Type: application/json" \
      "https://company.jfrog.io/xray/api/v1/ztr/patches" \
      -d @local-patches.json

    The API returns a JSON body with per-item results.

  4. Use a CycloneDX 1.6 declaration for each patch item. Exactly one component is the patched component. Its version must extend the original version with -local.<digits> or .local.<digits>. For example, 5.3.39-local.1.

    The following fields are required per patch item.

    FieldRequiredDescription
    local_repoYesKey of the local repository that holds the patched artifact.
    artifact_pathYesPath of the patched artifact inside local_repo. Do not start the path with /. Do not include empty, ., or .. segments, or the characters \, %, ?, or #.
    sha256YesSHA-256 of the stored artifact, 64 hexadecimal characters. Must match the CycloneDX SHA-256 hash.
    declarationYesCycloneDX 1.6 BOM that identifies the original coordinate and the remediated vulnerabilities.
    item_idNoCaller-defined identifier echoed in the result so a CI job can map each outcome to a build step.

    The following field applies at request level.

    FieldRequiredDescription
    patchesYesArray of patch items. Minimum one, maximum 100.
    create_evidenceNoDefault false. Set to true to create signed evidence for each accepted patch. Evidence creation doesn't reject the patch.
  5. Confirm the per-item status values of accepted, accepted_with_warnings, or rejected, and any messages codes. Accepted items are stored as Catalog candidates.

    Each accepted item includes an evidence object. Rejected items omit it. When create_evidence is false, evidence.status is skipped and evidence.reason is not_requested. The item status stays accepted.

    When create_evidence is true and evidence is created, evidence.status is created and the object includes id and uri. When evidence is skipped or fails, the item status is accepted_with_warnings. The warning code is evidence_skipped or evidence_failed, and field is /create_evidence. evidence.reason explains the outcome.

    📘

    Note

    Signed evidence requires an Enterprise Plus or Enterprise Plus Trial subscription, JFrog Artifactory 7.104.2 or later, and JFrog Evidence 7.232.0 or later. Your JFrog Platform also needs Zero-Touch Remediation and the Unified Security Bundle or the Ultimate Security Bundle.

    You can confirm that consuming Maven builds against the covered virtual repository receive the patched bytes. For more information, see View Remediation Requests.

Example Request Body

{
  "create_evidence": false,
  "patches": [
    {
      "item_id": "item-1",
      "local_repo": "patches-local",
      "artifact_path": "org/springframework/spring-core/5.3.39-local.1/spring-core-5.3.39-local.1.jar",
      "sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
      "declaration": {
        "bomFormat": "CycloneDX",
        "specVersion": "1.6",
        "version": 1,
        "metadata": {
          "component": {
            "bom-ref": "pkg:maven/org.springframework/[email protected]",
            "type": "library",
            "name": "org.springframework:spring-core",
            "version": "5.3.39-local.1",
            "purl": "pkg:maven/org.springframework/[email protected]"
          }
        },
        "components": [
          {
            "bom-ref": "pkg:maven/org.springframework/[email protected]",
            "type": "library",
            "name": "org.springframework:spring-core",
            "version": "5.3.39-local.1",
            "purl": "pkg:maven/org.springframework/[email protected]",
            "hashes": [
              {
                "alg": "SHA-256",
                "content": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
              }
            ],
            "properties": [
              { "name": "jfrog:component:original_name", "value": "org.springframework:spring-core" },
              { "name": "jfrog:component:original_version", "value": "5.3.39" },
              { "name": "jfrog:component:original_purl", "value": "pkg:maven/org.springframework/[email protected]" },
              { "name": "jfrog:component:onemodel_name", "value": "spring-core" },
              { "name": "jfrog:component:onemodel_type", "value": "maven" },
              { "name": "jfrog:component:onemodel_ecosystem", "value": "generic" },
              { "name": "jfrog:component:onemodel_namespace", "value": "org.springframework" },
              { "name": "jfrog:component:onemodel_version", "value": "5.3.39-local.1" }
            ]
          }
        ],
        "vulnerabilities": [
          {
            "id": "CVE-2024-22259",
            "analysis": { "state": "resolved" },
            "affects": [
              { "ref": "pkg:maven/org.springframework/[email protected]" }
            ]
          }
        ]
      }
    }
  ]
}

The patched component must include the original-coordinate properties (jfrog:component:original_name, original_version, original_purl) and the OneModel properties (onemodel_name, onemodel_type, onemodel_ecosystem, onemodel_namespace, onemodel_version). onemodel_type is maven and onemodel_ecosystem is generic. metadata.component must match the patched component identity. Each vulnerability affects.ref must match a declared bom-ref, and at least one must identify the patched component.

Example Response

A mixed batch still returns HTTP 200. Read results rather than inferring success from the status code.

{
  "accepted": 1,
  "rejected": 1,
  "results": [
    {
      "item_id": "item-1",
      "local_repo": "patches-local",
      "artifact_path": "org/springframework/spring-core/5.3.39-local.1/spring-core-5.3.39-local.1.jar",
      "status": "accepted",
      "messages": []
    },
    {
      "item_id": "item-2",
      "local_repo": "patches-local",
      "artifact_path": "org/example/example/1.0.0-local.1/example-1.0.0-local.1.jar",
      "status": "rejected",
      "messages": [
        {
          "code": "subject_mismatch",
          "severity": "error",
          "message": "artifact sha256 does not match request sha256",
          "field": "/patches/1/sha256"
        }
      ]
    }
  ]
}

accepted counts both accepted and accepted_with_warnings items. An item-level failure does not roll back items that succeeded. A request-level failure rejects the entire request and stores nothing. Request-level failures include invalid JSON, an empty patches array, a virtual_repo field, authentication failure, and a payload over 1 MiB or 100 items. If Zero-Touch Remediation is disabled, the API returns HTTP 404.

Don't send virtual_repo. The field is not accepted. At request level it returns HTTP 400. On an individual patch item it rejects that item with field_not_allowed.

Validation Outcomes

Zero-Touch Remediation rejects a patch when the registration cannot work. A skipped or failed evidence request is a warning on an accepted item. The patch is still stored. The following conditions describe typical validation outcomes.

ConditionOutcomeTypical code
virtual_repo on a patch itemRejectfield_not_allowed
Local repository is not indexed in XrayRejectrepo_not_indexed
Named repository is not a local repositoryRejectrepo_not_local
Named repository does not existRejectrepo_not_found
Artifact is missing at artifact_pathRejectartifact_not_found
SHA-256 does not match the stored artifact or the CycloneDX subjectRejectsubject_mismatch
CycloneDX is malformed, specVersion is not 1.6, or the patched version is not {original}-local.<digits> / {original}.local.<digits>Rejectmalformed_declaration
A required original-coordinate or OneModel property is missingRejectmissing_property
Caller lacks Manage Data permission on the artifactRejectpermission_denied
Catalog ingest fails after validationRejectcatalog_ingest_failed
create_evidence is true and evidence is skipped or failsAccept with warningevidence_skipped or evidence_failed
📘

Note

More than 100 patches or a body larger than 1 MiB returns HTTP 413. Invalid JSON, an empty patches array, or a request-level virtual_repo field returns HTTP 400. If Zero-Touch Remediation is disabled, the API returns HTTP 404.

After a patch is accepted, Catalog can return it when the covered Maven virtual repository lists that local repository as a member. Artifactory sends a blank URL for local members. Removing the local repository from the virtual repository stops serving those patches. There is no separate local-patch control on the automation Scope step.

Frequently Asked Questions

This section provides answers to frequently asked questions.

plusFAQs
Q: How do I register a local Maven patch?

A: Upload the patched artifact to an Xray-indexed local repository, then send POST /xray/api/v1/ztr/patches with a CycloneDX 1.6 declaration. For more information, see How Registration Fits the Remediation Flow.

Q: What happens if the SHA-256 in the request does not match the stored file?

A: Zero-Touch Remediation rejects that item with subject_mismatch. HTTP 200 on the batch does not mean every item was stored. For more information, see Validation Outcomes.

Q: What is the difference between HTTP 200 and an accepted patch?

A: HTTP 200 means the batch was processed. Inspect every results item for accepted, accepted_with_warnings, or rejected. For more information, see Example Response.

Q: What permissions do I need to register local patches?

A: You need Manage Data permission on each artifact you register, plus an Xray-indexed Maven local repository that is a member of a covered virtual repository. For more information, see Prerequisites.

Q: How do I create signed evidence for a local patch?

A: Set create_evidence to true on POST /xray/api/v1/ztr/patches. An accepted item returns evidence.status of created, with id and uri. If evidence is skipped or fails, the patch is still accepted and the item status is accepted_with_warnings. For more information, see How to register local patches.

Q: Can npm lockfile healing use local patches?

A: No. Local patches are Maven only. npm lockfile healing does not consume locally registered patches. For more information, see Zero-Touch Remediation Overview.

Related Topics


Did this page help you?