{
  "parent_root_cause": "Premmerce Wishlist for WooCommerce <= 1.1.11 reads the attacker-controlled premmerce_wishlist cookie (a JSON array of wishlist 'keys') via WishlistStorage::cookieGet() with no per-element validation, then interpolates those keys directly into SQL IN (...) literals built by string concatenation in WishlistModel::getWishlistsByKeys() / getDefaultWishlistByKeys() / setUserToWishlistsByKeys() (and other sinks). This lets an unauthenticated remote attacker break out of the quoted literal and inject arbitrary SQL.",
  "variant_root_cause": "Same: every alternate entry point tested (REST /add/popup GET, wc-ajax premmerce_wishlist_popup GET, REST /add POST, frontend wishlist page GET, and the wp_login assignUserWishlistFromCookie UPDATE path) funnels through the SAME cookieGet() -> concatenation-sink data flow. The cookie is the untrusted input; the concatenation sink is the bug. No variant introduced a different root cause.",
  "equivalence": "identical_root_cause",
  "confidence": "high",
  "rationale": "All candidates share: (1) the same untrusted input source (premmerce_wishlist cookie, attacker-controlled, no trust-boundary crossing beyond the HTTP request itself), (2) the same choke point (WishlistStorage::cookieGet()), and (3) the same sink family (string-concatenation IN (...) in WishlistModel). They differ only in the HTTP entry point that triggers getWishlistsAll()/addProductToWishlist()/Frontend::wishlistPage()/assignUserWishlistFromCookie() to call cookieGet() and a concatenation sink. Per the variant taxonomy this makes them alternate triggers of the SAME root cause, not a new bug. They are NOT a bypass because the 1.1.12 fix closes the choke point (cookieGet regex) AND parameterizes every sink, so every alternate trigger is blocked on the fixed build.",
  "sink_inventory_vuln_1_1_11": [
    "src/Models/WishlistModel.php:251-254 getWishlistsByKeys IN (\"...\") double-quote concat",
    "src/Models/WishlistModel.php:275-278 getDefaultWishlistByKeys IN (\"...\") double-quote concat",
    "src/Models/WishlistModel.php:449 getWishlists where_in IN (raw value) concat",
    "src/Models/WishlistModel.php:466 getWishlistsByProductId LIKE '%...%' concat",
    "src/Models/WishlistModel.php:579 setUserToWishlistsByKeys IN ('...') single-quote concat",
    "src/Models/WishlistModel.php:661 deleteWishlists IN (raw ids) concat"
  ],
  "sink_inventory_fixed_1_1_12": "All six sinks above converted to $wpdb->prepare() with %s/%d placeholders (and getWishlists where_in gains an allowed_columns allow-list; deleteWishlists ids pass array_map('intval'); getWishlistsByProductId uses esc_like + prepare). No data-interpolation sink remains.",
  "entrypoint_inventory_unauthenticated_to_cookie_sink": [
    "GET /?rest_route=/premmerce/wishlist/add/popup -> wishlistAddPopupRest -> wishlistAddPopup -> getWishlistsAll -> getWishlistsByKeys(cookieGet())  [parent repro surface; C0]",
    "GET /?wc-ajax=premmerce_wishlist_popup -> wishlistAddPopupAjax -> wishlistAddPopup -> getWishlistsAll -> getWishlistsByKeys(cookieGet())  [C1]",
    "POST /?rest_route=/premmerce/wishlist/add -> wishlistAdd -> addProductToWishlist -> getDefaultWishlistByKeys(cookieGet())  [C2]",
    "GET /?page_id=<wishlist_page> -> Frontend::wishlistPage -> getWishlistsByKeys(cookieGet())  [C3]",
    "wp_login -> Frontend::assignUserWishlistFromCookie -> setUserToWishlistsByKeys(userId, cookieGet())  [C4, authenticated trigger]"
  ]
}
