From 072f67d6e71af3d7fa6f5f4c73ae460d6844f511 Mon Sep 17 00:00:00 2001
From: Chocolate Pie <106949016+chocolate-pie@users.noreply.github.com>
Date: Sat, 6 Jan 2024 20:14:33 +0900
Subject: [PATCH] feat: Add support for mCaptcha (#12905)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* feat: Add support for mCaptcha

* fix: Fix docker compose configuration

* chore(frontend/docs): update changelog & fix eslint errors

* `@mcaptcha/vanilla-glue`をダイナミックインポートするように

* chore: Add missing prefix to CHANGELOG

* refactor(backend): 適当につけた変数の名前を変更
---
 .config/docker_example.env                    |   1 +
 CHANGELOG.md                                  |   3 +
 docker-compose_example.yml                    |  31 ++++++
 locales/index.d.ts                            |   5 +
 locales/ja-JP.yml                             |   5 +
 .../1704373210054-support-mcaptcha.js         |  22 ++++
 packages/backend/src/core/CaptchaService.ts   |  31 ++++++
 packages/backend/src/models/Meta.ts           |  25 ++++-
 .../src/server/api/SignupApiService.ts        |   7 ++
 .../src/server/api/endpoints/admin/meta.ts    |  20 ++++
 .../server/api/endpoints/admin/update-meta.ts |  22 +++-
 .../backend/src/server/api/endpoints/meta.ts  |  15 +++
 packages/frontend/package.json                |   1 +
 .../frontend/src/components/MkCaptcha.vue     |  41 +++++--
 .../src/components/MkSignupDialog.form.vue    |   4 +
 packages/frontend/src/index.html              |   6 +-
 .../src/pages/admin/bot-protection.vue        |  34 +++++-
 .../frontend/src/pages/admin/security.vue     |   3 +
 .../misskey-js/src/autogen/apiClientJSDoc.ts  |   2 +-
 packages/misskey-js/src/autogen/endpoint.ts   |   2 +-
 packages/misskey-js/src/autogen/entities.ts   |   2 +-
 packages/misskey-js/src/autogen/models.ts     |   2 +-
 packages/misskey-js/src/autogen/types.ts      |  13 ++-
 pnpm-lock.yaml                                | 101 ++++++++++--------
 24 files changed, 336 insertions(+), 62 deletions(-)
 create mode 100644 packages/backend/migration/1704373210054-support-mcaptcha.js

diff --git a/.config/docker_example.env b/.config/docker_example.env
index 7a0261524b..4fe8e76b78 100644
--- a/.config/docker_example.env
+++ b/.config/docker_example.env
@@ -2,3 +2,4 @@
 POSTGRES_PASSWORD=example-misskey-pass
 POSTGRES_USER=example-misskey-user
 POSTGRES_DB=misskey
+DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f7e1ac6a78..34b598224a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,9 @@
 
 ## 202x.x.x (Unreleased)
 
+### General
+- Feat: [mCaptcha](https://github.com/mCaptcha/mCaptcha)のサポートを追加
+
 ### Client
 - Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように
 - Fix: v2023.12.0で追加された「モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能」が管理画面上で正しく表示されていない問題を修正
diff --git a/docker-compose_example.yml b/docker-compose_example.yml
index 60ba4dc8ca..5cebbe4164 100644
--- a/docker-compose_example.yml
+++ b/docker-compose_example.yml
@@ -7,6 +7,7 @@ services:
     links:
       - db
       - redis
+#     - mcaptcha
 #     - meilisearch
     depends_on:
       db:
@@ -48,6 +49,36 @@ services:
       interval: 5s
       retries: 20
 
+#  mcaptcha:
+#    restart: always
+#    image: mcaptcha/mcaptcha:latest
+#    networks:
+#      internal_network:
+#      external_network:
+#        aliases:
+#          - localhost
+#    ports:
+#      - 7493:7493
+#    env_file:
+#      - .config/docker.env
+#    environment:
+#      PORT: 7493
+#      MCAPTCHA_redis_URL: "redis://mcaptcha_redis/"
+#    depends_on:
+#      db:
+#        condition: service_healthy
+#      mcaptcha_redis:
+#        condition: service_healthy
+#
+#  mcaptcha_redis:
+#    image: mcaptcha/cache:latest
+#    networks:
+#      - internal_network
+#    healthcheck:
+#      test: "redis-cli ping"
+#      interval: 5s
+#      retries: 20
+
 #  meilisearch:
 #    restart: always
 #    image: getmeili/meilisearch:v1.3.4
diff --git a/locales/index.d.ts b/locales/index.d.ts
index 3937784153..99bc0fc04f 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -382,6 +382,11 @@ export interface Locale {
     "enableHcaptcha": string;
     "hcaptchaSiteKey": string;
     "hcaptchaSecretKey": string;
+    "mcaptcha": string;
+    "enableMcaptcha": string;
+    "mcaptchaSiteKey": string;
+    "mcaptchaSecretKey": string;
+    "mcaptchaInstanceUrl": string;
     "recaptcha": string;
     "enableRecaptcha": string;
     "recaptchaSiteKey": string;
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 77f9a9ec0f..7cf5663a72 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -379,6 +379,11 @@ hcaptcha: "hCaptcha"
 enableHcaptcha: "hCaptchaを有効にする"
 hcaptchaSiteKey: "サイトキー"
 hcaptchaSecretKey: "シークレットキー"
+mcaptcha: "mCaptcha"
+enableMcaptcha: "mCaptchaを有効にする"
+mcaptchaSiteKey: "サイトキー"
+mcaptchaSecretKey: "シークレットキー"
+mcaptchaInstanceUrl: "mCaptchaのインスタンスのURL"
 recaptcha: "reCAPTCHA"
 enableRecaptcha: "reCAPTCHAを有効にする"
 recaptchaSiteKey: "サイトキー"
diff --git a/packages/backend/migration/1704373210054-support-mcaptcha.js b/packages/backend/migration/1704373210054-support-mcaptcha.js
new file mode 100644
index 0000000000..ce42b90716
--- /dev/null
+++ b/packages/backend/migration/1704373210054-support-mcaptcha.js
@@ -0,0 +1,22 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class SupportMcaptcha1704373210054 {
+    name = 'SupportMcaptcha1704373210054'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" ADD "enableMcaptcha" boolean NOT NULL DEFAULT false`);
+        await queryRunner.query(`ALTER TABLE "meta" ADD "mcaptchaSitekey" character varying(1024)`);
+        await queryRunner.query(`ALTER TABLE "meta" ADD "mcaptchaSecretKey" character varying(1024)`);
+        await queryRunner.query(`ALTER TABLE "meta" ADD "mcaptchaInstanceUrl" character varying(1024)`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "mcaptchaInstanceUrl"`);
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "mcaptchaSecretKey"`);
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "mcaptchaSitekey"`);
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableMcaptcha"`);
+    }
+}
diff --git a/packages/backend/src/core/CaptchaService.ts b/packages/backend/src/core/CaptchaService.ts
index f64196f4fc..6c5ee4835d 100644
--- a/packages/backend/src/core/CaptchaService.ts
+++ b/packages/backend/src/core/CaptchaService.ts
@@ -73,6 +73,37 @@ export class CaptchaService {
 		}
 	}
 
+	// https://codeberg.org/Gusted/mCaptcha/src/branch/main/mcaptcha.go
+	@bindThis
+	public async verifyMcaptcha(secret: string, siteKey: string, instanceHost: string, response: string | null | undefined): Promise<void> {
+		if (response == null) {
+			throw new Error('mcaptcha-failed: no response provided');
+		}
+
+		const endpointUrl = new URL('/api/v1/pow/siteverify', instanceHost);
+		const result = await this.httpRequestService.send(endpointUrl.toString(), {
+			method: 'POST',
+			body: JSON.stringify({
+				key: siteKey,
+				secret: secret,
+				token: response,
+			}),
+			headers: {
+				'Content-Type': 'application/json',
+			},
+		});
+
+		if (result.status !== 200) {
+			throw new Error('mcaptcha-failed: mcaptcha didn\'t return 200 OK');
+		}
+
+		const resp = (await result.json()) as { valid: boolean };
+
+		if (!resp.valid) {
+			throw new Error('mcaptcha-request-failed');
+		}
+	}
+
 	@bindThis
 	public async verifyTurnstile(secret: string, response: string | null | undefined): Promise<void> {
 		if (response == null) {
diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts
index f5a75ed28a..3265e85dd7 100644
--- a/packages/backend/src/models/Meta.ts
+++ b/packages/backend/src/models/Meta.ts
@@ -191,6 +191,29 @@ export class MiMeta {
 	})
 	public hcaptchaSecretKey: string | null;
 
+	@Column('boolean', {
+		default: false,
+	})
+	public enableMcaptcha: boolean;
+
+	@Column('varchar', {
+		length: 1024,
+		nullable: true,
+	})
+	public mcaptchaSitekey: string | null;
+
+	@Column('varchar', {
+		length: 1024,
+		nullable: true,
+	})
+	public mcaptchaSecretKey: string | null;
+
+	@Column('varchar', {
+		length: 1024,
+		nullable: true,
+	})
+	public mcaptchaInstanceUrl: string | null;
+
 	@Column('boolean', {
 		default: false,
 	})
@@ -467,7 +490,7 @@ export class MiMeta {
 		nullable: true,
 	})
 	public truemailInstance: string | null;
-	
+
 	@Column('varchar', {
 		length: 1024,
 		nullable: true,
diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts
index 753984ef52..6b4d9d9f70 100644
--- a/packages/backend/src/server/api/SignupApiService.ts
+++ b/packages/backend/src/server/api/SignupApiService.ts
@@ -65,6 +65,7 @@ export class SignupApiService {
 				'hcaptcha-response'?: string;
 				'g-recaptcha-response'?: string;
 				'turnstile-response'?: string;
+				'm-captcha-response'?: string;
 			}
 		}>,
 		reply: FastifyReply,
@@ -82,6 +83,12 @@ export class SignupApiService {
 				});
 			}
 
+			if (instance.enableMcaptcha && instance.mcaptchaSecretKey && instance.mcaptchaSitekey && instance.mcaptchaInstanceUrl) {
+				await this.captchaService.verifyMcaptcha(instance.mcaptchaSecretKey, instance.mcaptchaSitekey, instance.mcaptchaInstanceUrl, body['m-captcha-response']).catch(err => {
+					throw new FastifyReplyError(400, err);
+				});
+			}
+
 			if (instance.enableRecaptcha && instance.recaptchaSecretKey) {
 				await this.captchaService.verifyRecaptcha(instance.recaptchaSecretKey, body['g-recaptcha-response']).catch(err => {
 					throw new FastifyReplyError(400, err);
diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts
index 281f6c484c..0627c5055c 100644
--- a/packages/backend/src/server/api/endpoints/admin/meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/meta.ts
@@ -41,6 +41,18 @@ export const meta = {
 				type: 'string',
 				optional: false, nullable: true,
 			},
+			enableMcaptcha: {
+				type: 'boolean',
+				optional: false, nullable: false,
+			},
+			mcaptchaSiteKey: {
+				type: 'string',
+				optional: false, nullable: true,
+			},
+			mcaptchaInstanceUrl: {
+				type: 'string',
+				optional: false, nullable: true,
+			},
 			enableRecaptcha: {
 				type: 'boolean',
 				optional: false, nullable: false,
@@ -163,6 +175,10 @@ export const meta = {
 				type: 'string',
 				optional: false, nullable: true,
 			},
+			mcaptchaSecretKey: {
+				type: 'string',
+				optional: false, nullable: true,
+			},
 			recaptchaSecretKey: {
 				type: 'string',
 				optional: false, nullable: true,
@@ -468,6 +484,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				emailRequiredForSignup: instance.emailRequiredForSignup,
 				enableHcaptcha: instance.enableHcaptcha,
 				hcaptchaSiteKey: instance.hcaptchaSiteKey,
+				enableMcaptcha: instance.enableMcaptcha,
+				mcaptchaSiteKey: instance.mcaptchaSitekey,
+				mcaptchaInstanceUrl: instance.mcaptchaInstanceUrl,
 				enableRecaptcha: instance.enableRecaptcha,
 				recaptchaSiteKey: instance.recaptchaSiteKey,
 				enableTurnstile: instance.enableTurnstile,
@@ -498,6 +517,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				sensitiveWords: instance.sensitiveWords,
 				preservedUsernames: instance.preservedUsernames,
 				hcaptchaSecretKey: instance.hcaptchaSecretKey,
+				mcaptchaSecretKey: instance.mcaptchaSecretKey,
 				recaptchaSecretKey: instance.recaptchaSecretKey,
 				turnstileSecretKey: instance.turnstileSecretKey,
 				sensitiveMediaDetection: instance.sensitiveMediaDetection,
diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
index 3a6426435d..d76d3dfeea 100644
--- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
@@ -63,6 +63,10 @@ export const paramDef = {
 		enableHcaptcha: { type: 'boolean' },
 		hcaptchaSiteKey: { type: 'string', nullable: true },
 		hcaptchaSecretKey: { type: 'string', nullable: true },
+		enableMcaptcha: { type: 'boolean' },
+		mcaptchaSiteKey: { type: 'string', nullable: true },
+		mcaptchaInstanceUrl: { type: 'string', nullable: true },
+		mcaptchaSecretKey: { type: 'string', nullable: true },
 		enableRecaptcha: { type: 'boolean' },
 		recaptchaSiteKey: { type: 'string', nullable: true },
 		recaptchaSecretKey: { type: 'string', nullable: true },
@@ -269,6 +273,22 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				set.hcaptchaSecretKey = ps.hcaptchaSecretKey;
 			}
 
+			if (ps.enableMcaptcha !== undefined) {
+				set.enableMcaptcha = ps.enableMcaptcha;
+			}
+
+			if (ps.mcaptchaSiteKey !== undefined) {
+				set.mcaptchaSitekey = ps.mcaptchaSiteKey;
+			}
+
+			if (ps.mcaptchaInstanceUrl !== undefined) {
+				set.mcaptchaInstanceUrl = ps.mcaptchaInstanceUrl;
+			}
+
+			if (ps.mcaptchaSecretKey !== undefined) {
+				set.mcaptchaSecretKey = ps.mcaptchaSecretKey;
+			}
+
 			if (ps.enableRecaptcha !== undefined) {
 				set.enableRecaptcha = ps.enableRecaptcha;
 			}
@@ -472,7 +492,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 					set.verifymailAuthKey = ps.verifymailAuthKey;
 				}
 			}
-			
+
 			if (ps.enableTruemailApi !== undefined) {
 				set.enableTruemailApi = ps.enableTruemailApi;
 			}
diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts
index f7c2962bc2..529e82678d 100644
--- a/packages/backend/src/server/api/endpoints/meta.ts
+++ b/packages/backend/src/server/api/endpoints/meta.ts
@@ -108,6 +108,18 @@ export const meta = {
 				type: 'string',
 				optional: false, nullable: true,
 			},
+			enableMcaptcha: {
+				type: 'boolean',
+				optional: false, nullable: false,
+			},
+			mcaptchaSiteKey: {
+				type: 'string',
+				optional: false, nullable: true,
+			},
+			mcaptchaInstanceUrl: {
+				type: 'string',
+				optional: false, nullable: true,
+			},
 			enableRecaptcha: {
 				type: 'boolean',
 				optional: false, nullable: false,
@@ -351,6 +363,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				emailRequiredForSignup: instance.emailRequiredForSignup,
 				enableHcaptcha: instance.enableHcaptcha,
 				hcaptchaSiteKey: instance.hcaptchaSiteKey,
+				enableMcaptcha: instance.enableMcaptcha,
+				mcaptchaSiteKey: instance.mcaptchaSitekey,
+				mcaptchaInstanceUrl: instance.mcaptchaInstanceUrl,
 				enableRecaptcha: instance.enableRecaptcha,
 				recaptchaSiteKey: instance.recaptchaSiteKey,
 				enableTurnstile: instance.enableTurnstile,
diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index 864779fd9d..7e7559d825 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -19,6 +19,7 @@
 	"dependencies": {
 		"@discordapp/twemoji": "15.0.2",
 		"@github/webauthn-json": "2.1.1",
+		"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
 		"@misskey-dev/browser-image-resizer": "2.2.1-misskey.10",
 		"@rollup/plugin-json": "6.1.0",
 		"@rollup/plugin-replace": "5.0.5",
diff --git a/packages/frontend/src/components/MkCaptcha.vue b/packages/frontend/src/components/MkCaptcha.vue
index 40bca11e64..f60c721eae 100644
--- a/packages/frontend/src/components/MkCaptcha.vue
+++ b/packages/frontend/src/components/MkCaptcha.vue
@@ -6,12 +6,16 @@ SPDX-License-Identifier: AGPL-3.0-only
 <template>
 <div>
 	<span v-if="!available">{{ i18n.ts.waiting }}<MkEllipsis/></span>
-	<div ref="captchaEl"></div>
+	<div v-if="props.provider == 'mcaptcha'">
+		<div id="mcaptcha__widget-container" class="m-captcha-style"></div>
+		<div ref="captchaEl"></div>
+	</div>
+	<div v-else ref="captchaEl"></div>
 </div>
 </template>
 
 <script lang="ts" setup>
-import { ref, shallowRef, computed, onMounted, onBeforeUnmount, watch } from 'vue';
+import { ref, shallowRef, computed, onMounted, onBeforeUnmount, watch, onUnmounted } from 'vue';
 import { defaultStore } from '@/store.js';
 import { i18n } from '@/i18n.js';
 
@@ -26,7 +30,7 @@ export type Captcha = {
 	getResponse(id: string): string;
 };
 
-export type CaptchaProvider = 'hcaptcha' | 'recaptcha' | 'turnstile';
+export type CaptchaProvider = 'hcaptcha' | 'recaptcha' | 'turnstile' | 'mcaptcha';
 
 type CaptchaContainer = {
 	readonly [_ in CaptchaProvider]?: Captcha;
@@ -39,6 +43,7 @@ declare global {
 const props = defineProps<{
 	provider: CaptchaProvider;
 	sitekey: string | null; // null will show error on request
+	instanceUrl?: string | null;
 	modelValue?: string | null;
 }>();
 
@@ -55,6 +60,7 @@ const variable = computed(() => {
 		case 'hcaptcha': return 'hcaptcha';
 		case 'recaptcha': return 'grecaptcha';
 		case 'turnstile': return 'turnstile';
+		case 'mcaptcha': return 'mcaptcha';
 	}
 });
 
@@ -65,6 +71,7 @@ const src = computed(() => {
 		case 'hcaptcha': return 'https://js.hcaptcha.com/1/api.js?render=explicit&recaptchacompat=off';
 		case 'recaptcha': return 'https://www.recaptcha.net/recaptcha/api.js?render=explicit';
 		case 'turnstile': return 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit';
+		case 'mcaptcha': return null;
 	}
 });
 
@@ -72,9 +79,9 @@ const scriptId = computed(() => `script-${props.provider}`);
 
 const captcha = computed<Captcha>(() => window[variable.value] || {} as unknown as Captcha);
 
-if (loaded) {
+if (loaded || props.provider === 'mcaptcha') {
 	available.value = true;
-} else {
+} else if (src.value !== null) {
 	(document.getElementById(scriptId.value) ?? document.head.appendChild(Object.assign(document.createElement('script'), {
 		async: true,
 		id: scriptId.value,
@@ -87,7 +94,7 @@ function reset() {
 	if (captcha.value.reset) captcha.value.reset();
 }
 
-function requestRender() {
+async function requestRender() {
 	if (captcha.value.render && captchaEl.value instanceof Element) {
 		captcha.value.render(captchaEl.value, {
 			sitekey: props.sitekey,
@@ -96,6 +103,15 @@ function requestRender() {
 			'expired-callback': callback,
 			'error-callback': callback,
 		});
+	} else if (props.provider === 'mcaptcha' && props.instanceUrl && props.sitekey) {
+		const { default: Widget } = await import('@mcaptcha/vanilla-glue');
+		// @ts-expect-error avoid typecheck error
+		new Widget({
+			siteKey: {
+				instanceUrl: new URL(props.instanceUrl),
+				key: props.sitekey,
+			},
+		});
 	} else {
 		window.setTimeout(requestRender, 1);
 	}
@@ -105,14 +121,27 @@ function callback(response?: string) {
 	emit('update:modelValue', typeof response === 'string' ? response : null);
 }
 
+function onReceivedMessage(message: MessageEvent) {
+	if (message.data.token) {
+		if (props.instanceUrl && new URL(message.origin).host === new URL(props.instanceUrl).host) {
+			callback(<string>message.data.token);
+		}
+	}
+}
+
 onMounted(() => {
 	if (available.value) {
+		window.addEventListener('message', onReceivedMessage);
 		requestRender();
 	} else {
 		watch(available, requestRender);
 	}
 });
 
+onUnmounted(() => {
+	window.removeEventListener('message', onReceivedMessage);
+});
+
 onBeforeUnmount(() => {
 	reset();
 });
diff --git a/packages/frontend/src/components/MkSignupDialog.form.vue b/packages/frontend/src/components/MkSignupDialog.form.vue
index c71330d62c..79e17c9aef 100644
--- a/packages/frontend/src/components/MkSignupDialog.form.vue
+++ b/packages/frontend/src/components/MkSignupDialog.form.vue
@@ -63,6 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				</template>
 			</MkInput>
 			<MkCaptcha v-if="instance.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" :class="$style.captcha" provider="hcaptcha" :sitekey="instance.hcaptchaSiteKey"/>
+			<MkCaptcha v-if="instance.enableMcaptcha" ref="mcaptcha" v-model="mCaptchaResponse" :class="$style.captcha" provider="mcaptcha" :sitekey="instance.mcaptchaSiteKey" :instanceUrl="instance.mcaptchaInstanceUrl"/>
 			<MkCaptcha v-if="instance.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" :class="$style.captcha" provider="recaptcha" :sitekey="instance.recaptchaSiteKey"/>
 			<MkCaptcha v-if="instance.enableTurnstile" ref="turnstile" v-model="turnstileResponse" :class="$style.captcha" provider="turnstile" :sitekey="instance.turnstileSiteKey"/>
 			<MkButton type="submit" :disabled="shouldDisableSubmitting" large gradate rounded data-cy-signup-submit style="margin: 0 auto;">
@@ -117,6 +118,7 @@ const passwordStrength = ref<'' | 'low' | 'medium' | 'high'>('');
 const passwordRetypeState = ref<null | 'match' | 'not-match'>(null);
 const submitting = ref<boolean>(false);
 const hCaptchaResponse = ref<string | null>(null);
+const mCaptchaResponse = ref<string | null>(null);
 const reCaptchaResponse = ref<string | null>(null);
 const turnstileResponse = ref<string | null>(null);
 const usernameAbortController = ref<null | AbortController>(null);
@@ -125,6 +127,7 @@ const emailAbortController = ref<null | AbortController>(null);
 const shouldDisableSubmitting = computed((): boolean => {
 	return submitting.value ||
 		instance.enableHcaptcha && !hCaptchaResponse.value ||
+		instance.enableMcaptcha && !mCaptchaResponse.value ||
 		instance.enableRecaptcha && !reCaptchaResponse.value ||
 		instance.enableTurnstile && !turnstileResponse.value ||
 		instance.emailRequiredForSignup && emailState.value !== 'ok' ||
@@ -252,6 +255,7 @@ async function onSubmit(): Promise<void> {
 			emailAddress: email.value,
 			invitationCode: invitationCode.value,
 			'hcaptcha-response': hCaptchaResponse.value,
+			'm-captcha-response': mCaptchaResponse.value,
 			'g-recaptcha-response': reCaptchaResponse.value,
 			'turnstile-response': turnstileResponse.value,
 		});
diff --git a/packages/frontend/src/index.html b/packages/frontend/src/index.html
index 8de01e4802..13f800c72f 100644
--- a/packages/frontend/src/index.html
+++ b/packages/frontend/src/index.html
@@ -16,13 +16,13 @@
 	<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
 	<meta
 		http-equiv="Content-Security-Policy"
-		content="default-src 'self';
+		content="default-src 'self' https://newassets.hcaptcha.com/ https://challenges.cloudflare.com/ http://localhost:7493/;
 			worker-src 'self';
-			script-src 'self' 'unsafe-eval';
+			script-src 'self' 'unsafe-eval' https://*.hcaptcha.com https://challenges.cloudflare.com;
 			style-src 'self' 'unsafe-inline';
 			img-src 'self' data: www.google.com xn--931a.moe localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;
 			media-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;
-			connect-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;"
+			connect-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000 https://newassets.hcaptcha.com;"
 	/>
 	<meta property="og:site_name" content="[DEV BUILD] Misskey" />
 	<meta name="viewport" content="width=device-width, initial-scale=1">
diff --git a/packages/frontend/src/pages/admin/bot-protection.vue b/packages/frontend/src/pages/admin/bot-protection.vue
index 99b8070b71..37f8227485 100644
--- a/packages/frontend/src/pages/admin/bot-protection.vue
+++ b/packages/frontend/src/pages/admin/bot-protection.vue
@@ -10,6 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<MkRadios v-model="provider">
 				<option :value="null">{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</option>
 				<option value="hcaptcha">hCaptcha</option>
+				<option value="mcaptcha">mCaptcha</option>
 				<option value="recaptcha">reCAPTCHA</option>
 				<option value="turnstile">Turnstile</option>
 			</MkRadios>
@@ -28,6 +29,24 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<MkCaptcha provider="hcaptcha" :sitekey="hcaptchaSiteKey || '10000000-ffff-ffff-ffff-000000000001'"/>
 				</FormSlot>
 			</template>
+			<template v-else-if="provider === 'mcaptcha'">
+				<MkInput v-model="mcaptchaSiteKey">
+					<template #prefix><i class="ti ti-key"></i></template>
+					<template #label>{{ i18n.ts.mcaptchaSiteKey }}</template>
+				</MkInput>
+				<MkInput v-model="mcaptchaSecretKey">
+					<template #prefix><i class="ti ti-key"></i></template>
+					<template #label>{{ i18n.ts.mcaptchaSecretKey }}</template>
+				</MkInput>
+				<MkInput v-model="mcaptchaInstanceUrl">
+					<template #prefix><i class="ti ti-link"></i></template>
+					<template #label>{{ i18n.ts.mcaptchaInstanceUrl }}</template>
+				</MkInput>
+				<FormSlot v-if="mcaptchaSiteKey && mcaptchaInstanceUrl">
+					<template #label>{{ i18n.ts.preview }}</template>
+					<MkCaptcha provider="mcaptcha" :sitekey="mcaptchaSiteKey" :instanceUrl="mcaptchaInstanceUrl"/>
+				</FormSlot>
+			</template>
 			<template v-else-if="provider === 'recaptcha'">
 				<MkInput v-model="recaptchaSiteKey">
 					<template #prefix><i class="ti ti-key"></i></template>
@@ -81,6 +100,9 @@ const MkCaptcha = defineAsyncComponent(() => import('@/components/MkCaptcha.vue'
 const provider = ref<CaptchaProvider | null>(null);
 const hcaptchaSiteKey = ref<string | null>(null);
 const hcaptchaSecretKey = ref<string | null>(null);
+const mcaptchaSiteKey = ref<string | null>(null);
+const mcaptchaSecretKey = ref<string | null>(null);
+const mcaptchaInstanceUrl = ref<string | null>(null);
 const recaptchaSiteKey = ref<string | null>(null);
 const recaptchaSecretKey = ref<string | null>(null);
 const turnstileSiteKey = ref<string | null>(null);
@@ -90,12 +112,18 @@ async function init() {
 	const meta = await misskeyApi('admin/meta');
 	hcaptchaSiteKey.value = meta.hcaptchaSiteKey;
 	hcaptchaSecretKey.value = meta.hcaptchaSecretKey;
+	mcaptchaSiteKey.value = meta.mcaptchaSiteKey;
+	mcaptchaSecretKey.value = meta.mcaptchaSecretKey;
+	mcaptchaInstanceUrl.value = meta.mcaptchaInstanceUrl;
 	recaptchaSiteKey.value = meta.recaptchaSiteKey;
 	recaptchaSecretKey.value = meta.recaptchaSecretKey;
 	turnstileSiteKey.value = meta.turnstileSiteKey;
 	turnstileSecretKey.value = meta.turnstileSecretKey;
 
-	provider.value = meta.enableHcaptcha ? 'hcaptcha' : meta.enableRecaptcha ? 'recaptcha' : meta.enableTurnstile ? 'turnstile' : null;
+	provider.value = meta.enableHcaptcha ? 'hcaptcha' :
+		meta.enableRecaptcha ? 'recaptcha' :
+		meta.enableTurnstile ? 'turnstile' :
+		meta.enableMcaptcha ? 'mcaptcha' : null;
 }
 
 function save() {
@@ -103,6 +131,10 @@ function save() {
 		enableHcaptcha: provider.value === 'hcaptcha',
 		hcaptchaSiteKey: hcaptchaSiteKey.value,
 		hcaptchaSecretKey: hcaptchaSecretKey.value,
+		enableMcaptcha: provider.value === 'mcaptcha',
+		mcaptchaSiteKey: mcaptchaSiteKey.value,
+		mcaptchaSecretKey: mcaptchaSecretKey.value,
+		mcaptchaInstanceUrl: mcaptchaInstanceUrl.value,
 		enableRecaptcha: provider.value === 'recaptcha',
 		recaptchaSiteKey: recaptchaSiteKey.value,
 		recaptchaSecretKey: recaptchaSecretKey.value,
diff --git a/packages/frontend/src/pages/admin/security.vue b/packages/frontend/src/pages/admin/security.vue
index ec0c6166d0..a691d8ea1e 100644
--- a/packages/frontend/src/pages/admin/security.vue
+++ b/packages/frontend/src/pages/admin/security.vue
@@ -13,6 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<template #icon><i class="ti ti-shield"></i></template>
 					<template #label>{{ i18n.ts.botProtection }}</template>
 					<template v-if="enableHcaptcha" #suffix>hCaptcha</template>
+					<template v-else-if="enableMcaptcha" #suffix>mCaptcha</template>
 					<template v-else-if="enableRecaptcha" #suffix>reCAPTCHA</template>
 					<template v-else-if="enableTurnstile" #suffix>Turnstile</template>
 					<template v-else #suffix>{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</template>
@@ -155,6 +156,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
 
 const summalyProxy = ref<string>('');
 const enableHcaptcha = ref<boolean>(false);
+const enableMcaptcha = ref<boolean>(false);
 const enableRecaptcha = ref<boolean>(false);
 const enableTurnstile = ref<boolean>(false);
 const sensitiveMediaDetection = ref<string>('none');
@@ -174,6 +176,7 @@ async function init() {
 	const meta = await misskeyApi('admin/meta');
 	summalyProxy.value = meta.summalyProxy;
 	enableHcaptcha.value = meta.enableHcaptcha;
+	enableMcaptcha.value = meta.enableMcaptcha;
 	enableRecaptcha.value = meta.enableRecaptcha;
 	enableTurnstile.value = meta.enableTurnstile;
 	sensitiveMediaDetection.value = meta.sensitiveMediaDetection;
diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts
index e8722cab3b..43d80734e9 100644
--- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts
+++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts
@@ -1,6 +1,6 @@
 /*
  * version: 2023.12.2
- * generatedAt: 2024-01-02T08:53:57.449Z
+ * generatedAt: 2024-01-04T18:10:15.096Z
  */
 
 import type { SwitchCaseResponseType } from '../api.js';
diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts
index 192a1a31e0..07ee46ace9 100644
--- a/packages/misskey-js/src/autogen/endpoint.ts
+++ b/packages/misskey-js/src/autogen/endpoint.ts
@@ -1,6 +1,6 @@
 /*
  * version: 2023.12.2
- * generatedAt: 2024-01-02T08:53:57.445Z
+ * generatedAt: 2024-01-04T18:10:15.094Z
  */
 
 import type {
diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts
index fd4d7372cc..546d90ce21 100644
--- a/packages/misskey-js/src/autogen/entities.ts
+++ b/packages/misskey-js/src/autogen/entities.ts
@@ -1,6 +1,6 @@
 /*
  * version: 2023.12.2
- * generatedAt: 2024-01-02T08:53:57.443Z
+ * generatedAt: 2024-01-04T18:10:15.093Z
  */
 
 import { operations } from './types.js';
diff --git a/packages/misskey-js/src/autogen/models.ts b/packages/misskey-js/src/autogen/models.ts
index db0ada0f3b..59e4bc2f60 100644
--- a/packages/misskey-js/src/autogen/models.ts
+++ b/packages/misskey-js/src/autogen/models.ts
@@ -1,6 +1,6 @@
 /*
  * version: 2023.12.2
- * generatedAt: 2024-01-02T08:53:57.441Z
+ * generatedAt: 2024-01-04T18:10:15.091Z
  */
 
 import { components } from './types.js';
diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts
index 37c8f58f58..b62bd90eea 100644
--- a/packages/misskey-js/src/autogen/types.ts
+++ b/packages/misskey-js/src/autogen/types.ts
@@ -3,7 +3,7 @@
 
 /*
  * version: 2023.12.2
- * generatedAt: 2024-01-02T08:53:56.447Z
+ * generatedAt: 2024-01-04T18:10:15.023Z
  */
 
 /**
@@ -4400,6 +4400,9 @@ export type operations = {
             emailRequiredForSignup: boolean;
             enableHcaptcha: boolean;
             hcaptchaSiteKey: string | null;
+            enableMcaptcha: boolean;
+            mcaptchaSiteKey: string | null;
+            mcaptchaInstanceUrl: string | null;
             enableRecaptcha: boolean;
             recaptchaSiteKey: string | null;
             enableTurnstile: boolean;
@@ -4425,6 +4428,7 @@ export type operations = {
             bannedEmailDomains?: string[];
             preservedUsernames: string[];
             hcaptchaSecretKey: string | null;
+            mcaptchaSecretKey: string | null;
             recaptchaSecretKey: string | null;
             turnstileSecretKey: string | null;
             sensitiveMediaDetection: string;
@@ -8197,6 +8201,10 @@ export type operations = {
           enableHcaptcha?: boolean;
           hcaptchaSiteKey?: string | null;
           hcaptchaSecretKey?: string | null;
+          enableMcaptcha?: boolean;
+          mcaptchaSiteKey?: string | null;
+          mcaptchaInstanceUrl?: string | null;
+          mcaptchaSecretKey?: string | null;
           enableRecaptcha?: boolean;
           recaptchaSiteKey?: string | null;
           recaptchaSecretKey?: string | null;
@@ -18704,6 +18712,9 @@ export type operations = {
             emailRequiredForSignup: boolean;
             enableHcaptcha: boolean;
             hcaptchaSiteKey: string | null;
+            enableMcaptcha: boolean;
+            mcaptchaSiteKey: string | null;
+            mcaptchaInstanceUrl: string | null;
             enableRecaptcha: boolean;
             recaptchaSiteKey: string | null;
             enableTurnstile: boolean;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 562c90595e..28cfe3222f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -661,6 +661,9 @@ importers:
       '@github/webauthn-json':
         specifier: 2.1.1
         version: 2.1.1
+      '@mcaptcha/vanilla-glue':
+        specifier: 0.1.0-alpha-3
+        version: 0.1.0-alpha-3
       '@misskey-dev/browser-image-resizer':
         specifier: 2.2.1-misskey.10
         version: 2.2.1-misskey.10
@@ -1820,7 +1823,7 @@ packages:
       '@babel/traverse': 7.22.11
       '@babel/types': 7.22.17
       convert-source-map: 1.9.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       gensync: 1.0.0-beta.2
       json5: 2.2.3
       semver: 6.3.1
@@ -1843,7 +1846,7 @@ packages:
       '@babel/traverse': 7.23.5
       '@babel/types': 7.23.5
       convert-source-map: 2.0.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       gensync: 1.0.0-beta.2
       json5: 2.2.3
       semver: 6.3.1
@@ -1945,7 +1948,7 @@ packages:
       '@babel/core': 7.23.5
       '@babel/helper-compilation-targets': 7.22.15
       '@babel/helper-plugin-utils': 7.22.5
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       lodash.debounce: 4.0.8
       resolve: 1.22.8
     transitivePeerDependencies:
@@ -3345,7 +3348,7 @@ packages:
       '@babel/helper-split-export-declaration': 7.22.6
       '@babel/parser': 7.23.5
       '@babel/types': 7.22.17
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       globals: 11.12.0
     transitivePeerDependencies:
       - supports-color
@@ -3363,7 +3366,7 @@ packages:
       '@babel/helper-split-export-declaration': 7.22.6
       '@babel/parser': 7.23.5
       '@babel/types': 7.23.5
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       globals: 11.12.0
     transitivePeerDependencies:
       - supports-color
@@ -4242,7 +4245,7 @@ packages:
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dependencies:
       ajv: 6.12.6
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       espree: 9.6.1
       globals: 13.19.0
       ignore: 5.2.4
@@ -4259,7 +4262,7 @@ packages:
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dependencies:
       ajv: 6.12.6
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       espree: 9.6.1
       globals: 13.19.0
       ignore: 5.2.4
@@ -4524,7 +4527,7 @@ packages:
     engines: {node: '>=10.10.0'}
     dependencies:
       '@humanwhocodes/object-schema': 2.0.1
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       minimatch: 3.1.2
     transitivePeerDependencies:
       - supports-color
@@ -4902,6 +4905,16 @@ packages:
     dev: false
     optional: true
 
+  /@mcaptcha/core-glue@0.1.0-alpha-5:
+    resolution: {integrity: sha512-16qWm5O5X0Y9LXULULaAks8Vf9FNlUUBcR5KDt49aWhFhG5++JzxNmCwQM9EJSHNU7y0U+FdyAWcGmjfKlkRLA==}
+    dev: false
+
+  /@mcaptcha/vanilla-glue@0.1.0-alpha-3:
+    resolution: {integrity: sha512-GT6TJBgmViGXcXiT5VOr+h/6iOnThSlZuCoOWncubyTZU9R3cgU5vWPkF7G6Ob6ee2CBe3yqBxxk24CFVGTVXw==}
+    dependencies:
+      '@mcaptcha/core-glue': 0.1.0-alpha-5
+    dev: false
+
   /@mdx-js/react@2.3.0(react@18.2.0):
     resolution: {integrity: sha512-zQH//gdOmuu7nt2oJR29vFhDv88oGPmVw6BggmrHeMI+xgEkp1B2dX9/bMBSYtK0dyLX/aOmesKS09g222K1/g==}
     peerDependencies:
@@ -5084,7 +5097,7 @@ packages:
       '@open-draft/until': 1.0.3
       '@types/debug': 4.1.7
       '@xmldom/xmldom': 0.8.6
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       headers-polyfill: 3.2.5
       outvariant: 1.4.0
       strict-event-emitter: 0.2.8
@@ -7365,7 +7378,7 @@ packages:
     hasBin: true
     peerDependencies:
       '@swc/core': ^1.2.66
-      chokidar: 3.5.3
+      chokidar: ^3.5.1
     peerDependenciesMeta:
       chokidar:
         optional: true
@@ -8493,7 +8506,7 @@ packages:
       '@typescript-eslint/type-utils': 6.11.0(eslint@8.53.0)(typescript@5.3.3)
       '@typescript-eslint/utils': 6.11.0(eslint@8.53.0)(typescript@5.3.3)
       '@typescript-eslint/visitor-keys': 6.11.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       eslint: 8.53.0
       graphemer: 1.4.0
       ignore: 5.2.4
@@ -8522,7 +8535,7 @@ packages:
       '@typescript-eslint/type-utils': 6.14.0(eslint@8.56.0)(typescript@5.3.3)
       '@typescript-eslint/utils': 6.14.0(eslint@8.56.0)(typescript@5.3.3)
       '@typescript-eslint/visitor-keys': 6.14.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       eslint: 8.56.0
       graphemer: 1.4.0
       ignore: 5.2.4
@@ -8548,7 +8561,7 @@ packages:
       '@typescript-eslint/types': 6.11.0
       '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.3.3)
       '@typescript-eslint/visitor-keys': 6.11.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       eslint: 8.53.0
       typescript: 5.3.3
     transitivePeerDependencies:
@@ -8569,7 +8582,7 @@ packages:
       '@typescript-eslint/types': 6.14.0
       '@typescript-eslint/typescript-estree': 6.14.0(typescript@5.3.3)
       '@typescript-eslint/visitor-keys': 6.14.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       eslint: 8.56.0
       typescript: 5.3.3
     transitivePeerDependencies:
@@ -8604,7 +8617,7 @@ packages:
     dependencies:
       '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.3.3)
       '@typescript-eslint/utils': 6.11.0(eslint@8.53.0)(typescript@5.3.3)
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       eslint: 8.53.0
       ts-api-utils: 1.0.1(typescript@5.3.3)
       typescript: 5.3.3
@@ -8624,7 +8637,7 @@ packages:
     dependencies:
       '@typescript-eslint/typescript-estree': 6.14.0(typescript@5.3.3)
       '@typescript-eslint/utils': 6.14.0(eslint@8.56.0)(typescript@5.3.3)
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       eslint: 8.56.0
       ts-api-utils: 1.0.1(typescript@5.3.3)
       typescript: 5.3.3
@@ -8653,7 +8666,7 @@ packages:
     dependencies:
       '@typescript-eslint/types': 6.11.0
       '@typescript-eslint/visitor-keys': 6.11.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       globby: 11.1.0
       is-glob: 4.0.3
       semver: 7.5.4
@@ -8674,7 +8687,7 @@ packages:
     dependencies:
       '@typescript-eslint/types': 6.14.0
       '@typescript-eslint/visitor-keys': 6.14.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       globby: 11.1.0
       is-glob: 4.0.3
       semver: 7.5.4
@@ -9131,7 +9144,7 @@ packages:
     engines: {node: '>= 6.0.0'}
     requiresBuild: true
     dependencies:
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
 
@@ -9139,7 +9152,7 @@ packages:
     resolution: {integrity: sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==}
     engines: {node: '>= 14'}
     dependencies:
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
     dev: false
@@ -9514,7 +9527,7 @@ packages:
     resolution: {integrity: sha512-TAlMYvOuwGyLK3PfBb5WKBXZmXz2fVCgv23d6zZFdle/q3gPjmxBaeuC0pY0Dzs5PWMSgfqqEZkrye19GlDTgw==}
     dependencies:
       archy: 1.0.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       fastq: 1.15.0
     transitivePeerDependencies:
       - supports-color
@@ -10948,7 +10961,6 @@ packages:
     dependencies:
       ms: 2.1.2
       supports-color: 5.5.0
-    dev: true
 
   /debug@4.3.4(supports-color@8.1.1):
     resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
@@ -10961,6 +10973,7 @@ packages:
     dependencies:
       ms: 2.1.2
       supports-color: 8.1.1
+    dev: true
 
   /decamelize-keys@1.1.1:
     resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==}
@@ -11177,7 +11190,7 @@ packages:
     hasBin: true
     dependencies:
       address: 1.2.2
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
     dev: true
@@ -11501,7 +11514,7 @@ packages:
     peerDependencies:
       esbuild: '>=0.12 <1'
     dependencies:
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       esbuild: 0.18.20
     transitivePeerDependencies:
       - supports-color
@@ -11840,7 +11853,7 @@ packages:
       ajv: 6.12.6
       chalk: 4.1.2
       cross-spawn: 7.0.3
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       doctrine: 3.0.0
       escape-string-regexp: 4.0.0
       eslint-scope: 7.2.2
@@ -11887,7 +11900,7 @@ packages:
       ajv: 6.12.6
       chalk: 4.1.2
       cross-spawn: 7.0.3
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       doctrine: 3.0.0
       escape-string-regexp: 4.0.0
       eslint-scope: 7.2.2
@@ -12491,7 +12504,7 @@ packages:
       debug:
         optional: true
     dependencies:
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
 
   /for-each@0.3.3:
     resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
@@ -13043,7 +13056,6 @@ packages:
   /has-flag@3.0.0:
     resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
     engines: {node: '>=4'}
-    dev: true
 
   /has-flag@4.0.0:
     resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
@@ -13181,7 +13193,7 @@ packages:
     engines: {node: '>= 14'}
     dependencies:
       agent-base: 7.1.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
     dev: false
@@ -13243,7 +13255,7 @@ packages:
     engines: {node: '>= 6.0.0'}
     dependencies:
       agent-base: 5.1.1
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
     dev: true
@@ -13253,7 +13265,7 @@ packages:
     engines: {node: '>= 6'}
     dependencies:
       agent-base: 6.0.2
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
 
@@ -13262,7 +13274,7 @@ packages:
     engines: {node: '>= 14'}
     dependencies:
       agent-base: 7.1.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
     dev: false
@@ -13272,7 +13284,7 @@ packages:
     engines: {node: '>= 14'}
     dependencies:
       agent-base: 7.1.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
     dev: false
@@ -13422,7 +13434,7 @@ packages:
     dependencies:
       '@ioredis/commands': 1.2.0
       cluster-key-slot: 1.1.2
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       denque: 2.1.0
       lodash.defaults: 4.2.0
       lodash.isarguments: 3.1.0
@@ -13863,7 +13875,7 @@ packages:
     resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==}
     engines: {node: '>=10'}
     dependencies:
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       istanbul-lib-coverage: 3.2.0
       source-map: 0.6.1
     transitivePeerDependencies:
@@ -14541,7 +14553,7 @@ packages:
     resolution: {integrity: sha512-pJ4XLQP4Q9HTxl6RVDLJ8Cyh1uitSs0CzDBAz1uoJ4sRD/Bk7cFSXL1FUXDW3zJ7YnfliJx6eu8Jn283bpZ4Yg==}
     engines: {node: '>=10'}
     dependencies:
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       rfdc: 1.3.0
       uri-js: 4.4.1
     transitivePeerDependencies:
@@ -17109,7 +17121,7 @@ packages:
     engines: {node: '>=8.16.0'}
     dependencies:
       '@types/mime-types': 2.1.4
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       extract-zip: 1.7.0
       https-proxy-agent: 4.0.0
       mime: 2.6.0
@@ -18108,7 +18120,7 @@ packages:
     dependencies:
       '@hapi/hoek': 10.0.1
       '@hapi/wreck': 18.0.1
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       joi: 17.7.0
     transitivePeerDependencies:
       - supports-color
@@ -18308,7 +18320,7 @@ packages:
     engines: {node: '>= 14'}
     dependencies:
       agent-base: 7.1.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       socks: 2.7.1
     transitivePeerDependencies:
       - supports-color
@@ -18461,7 +18473,7 @@ packages:
       arg: 5.0.2
       bluebird: 3.7.2
       check-more-types: 2.24.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       execa: 5.1.1
       lazy-ass: 1.6.0
       ps-tree: 1.2.0
@@ -18726,7 +18738,6 @@ packages:
     engines: {node: '>=4'}
     dependencies:
       has-flag: 3.0.0
-    dev: true
 
   /supports-color@7.2.0:
     resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
@@ -19343,7 +19354,7 @@ packages:
       chalk: 4.1.2
       cli-highlight: 2.1.11
       date-fns: 2.30.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       dotenv: 16.0.3
       glob: 8.1.0
       ioredis: 5.3.2
@@ -19701,7 +19712,7 @@ packages:
     hasBin: true
     dependencies:
       cac: 6.7.14
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       mlly: 1.4.0
       pathe: 1.1.1
       picocolors: 1.0.0
@@ -19813,7 +19824,7 @@ packages:
       acorn-walk: 8.2.0
       cac: 6.7.14
       chai: 4.3.10
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       happy-dom: 10.0.3
       local-pkg: 0.4.3
       magic-string: 0.30.3
@@ -19895,7 +19906,7 @@ packages:
     peerDependencies:
       eslint: '>=6.0.0'
     dependencies:
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       eslint: 8.56.0
       eslint-scope: 7.2.2
       eslint-visitor-keys: 3.4.3
-- 
GitLab