{
  "variant_id": "CVE-2026-82329-variant-join-router-and-kid",
  "created_at": "2026-09-01T08:00:00Z",
  "variant_summary": "Alternate unauthenticated entry points and key-selection branches reaching the same blank-join-key sink as the parent exploit: (A) POST /access/api/v1/registry/join/router (RegistryNoAuthResource.joinRouter, second no-auth endpoint), (B) same with ?override=true, (C) POST /access/api/v1/registry/join with explicit kid=sha256('') claim. All three yield a never-expiring scp=admin service token and full admin takeover on vulnerable 7.146.25; all three are rejected (HTTP 400) on fixed 7.146.38, so this is a confirmed distinct VARIANT but NOT a bypass - the two-class fix (JoinKeyAccess blank filter + JoinKeyHashPair blank rejection) covers every path found.",
  "relation": "newer_version_sibling",
  "origin_kind": "pruva_variant",
  "repository": "https://releases-docker.jfrog.io/jfrog/artifactory-jcr",
  "submitted_target": {
    "target_kind": "docker_image",
    "version": "7.146.25",
    "ref": "releases-docker.jfrog.io/jfrog/artifactory-jcr:7.146.25",
    "display": "JFrog Artifactory JCR 7.146.25 (Access 7.176.15), image sha256:1734bd9277874ce6a29239fbbe0fe21befa9588ba59b21f724deaee77a4f9c57"
  },
  "variant_target": {
    "target_kind": "docker_image",
    "version": "7.146.38",
    "ref": "releases-docker.jfrog.io/jfrog/artifactory-jcr:7.146.38",
    "display": "JFrog Artifactory JCR 7.146.38 (Access 7.176.28, fixed), image sha256:a94c9a8e27a6542bdca359a928cbb50f6aa2732c135291a13e17bde3a5c3996b; proprietary build, no public commit SHA - build revision 84638900 from embedded artifactory.product.version.properties"
  },
  "same_root_cause_confidence": "high",
  "same_surface_confidence": "high",
  "claimed_surface": "api_remote",
  "validated_surface": "api_remote",
  "required_entrypoint_kind": "endpoint",
  "required_entrypoint_detail": "Unauthenticated POST /access/api/v1/registry/join/router (variant A/B) and POST /access/api/v1/registry/join with explicit kid claim (variant C) on self-hosted Artifactory; join JWT signed with the publicly derivable blank-join-key HMAC secret (32 x 0x20). Router variant returns HTTP 200 wrapper JWT whose 'token' claim carries the scp=admin service token; check_url validation skipped by omitting the check_url claim.",
  "attacker_controlled_input": "Unauthenticated HS256 join JWT (service_id, node_id, node_ip, skip_node_registration, iat and optionally kid claims chosen by attacker) signed with the blank-join-key HMAC secret (pkcs7('') = 32 bytes of 0x20), sent to /access/api/v1/registry/join/router[?override=true] or to /access/api/v1/registry/join with kid=sha256('')",
  "trigger_path": "RegistryNoAuthResource.joinRouter (POST /v1/registry/join/router) -> JoinServiceImpl.joinRouter -> getValidatedJwtToken -> getJoinKey -> JoinKeyAccess.getTokenSignatureVerifiers (blank additional join key, kid=sha256('')) -> registerWith -> ServiceTokenProviderImpl scope=admin token wrapped by combineTokenAndCertificate; variant C exercises the kid-selected branch of getRelevantJoinKeys via the parent endpoint. Admin takeover then via /access/api/v1/users + /access/api/v1/tokens + /artifactory/api/system/info.",
  "observed_impact_class": "authz_bypass",
  "exploitability_confidence": "high",
  "evidence_scope": "production_path",
  "runtime_manifest_present": true,
  "end_to_end_target_reached": true,
  "inferred": false,
  "claim_block_reason": null,
  "blocking_mitigation": "On fixed 7.146.38 all variants are rejected at the join step (HTTP 400): JoinKeyHashPair rejects blank keys and JoinKeyAccess.tryResolveJoinKeys filters blank entries, removing the blank key from the verification key set for every entry point and key-selection branch.",
  "file_path": "access-server-rest:org/jfrog/access/server/rest/resource/registry/RegistryNoAuthResource.java",
  "line_start": 59,
  "line_end": 67,
  "secondary_anchors": [
    {
      "file_path": "access-server-core:org/jfrog/access/server/service/registry/JoinServiceImpl.java",
      "line_start": 142,
      "line_end": 161
    },
    {
      "file_path": "access-server-core:org/jfrog/access/server/service/registry/JoinServiceImpl.java",
      "line_start": 225,
      "line_end": 261
    },
    {
      "file_path": "access-server-core:org/jfrog/access/server/startup/JoinKeyAccess.java",
      "line_start": 113,
      "line_end": 132
    }
  ],
  "review_scope_paths": [
    "access-server-rest:org/jfrog/access/server/rest/resource/registry/RegistryNoAuthResource",
    "access-server-core:org/jfrog/access/server/service/registry/JoinServiceImpl",
    "access-server-core:org/jfrog/access/server/startup/JoinKeyAccess",
    "access-common-api:org/jfrog/access/token/JoinKeyHashPair",
    "access-common-api:org/jfrog/access/util/JoinKeyUtils"
  ],
  "artifact_refs": {
    "variant_manifest": "vuln_variant/variant_manifest.json",
    "validation_verdict": "vuln_variant/validation_verdict.json",
    "runtime_manifest": "vuln_variant/runtime_manifest.json",
    "repro_log": "logs/vuln_variant/reproduction_steps.log",
    "root_cause_equivalence": "vuln_variant/root_cause_equivalence.json",
    "reproducer": [
      "vuln_variant/reproduction_steps.sh",
      "vuln_variant/exploit_join_router_variant.py"
    ]
  }
}
