diff --git a/packages/backend/package.json b/packages/backend/package.json
index f07a15bc54..2354c95646 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -35,7 +35,6 @@
 		"broadcast-channel": "4.10.0",
 		"bull": "4.8.1",
 		"cacheable-lookup": "6.0.4",
-		"cafy": "15.2.1",
 		"cbor": "8.1.0",
 		"chalk": "5.0.1",
 		"chalk-template": "0.4.0",
diff --git a/packages/backend/src/misc/cafy-id.ts b/packages/backend/src/misc/cafy-id.ts
deleted file mode 100644
index dd81c5c4cf..0000000000
--- a/packages/backend/src/misc/cafy-id.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { Context } from 'cafy';
-
-// eslint-disable-next-line @typescript-eslint/ban-types
-export class ID<Maybe = string> extends Context<string | (Maybe extends {} ? string : Maybe)> {
-	public readonly name = 'ID';
-
-	constructor(optional = false, nullable = false) {
-		super(optional, nullable);
-
-		this.push((v: any) => {
-			if (typeof v !== 'string') {
-				return new Error('must-be-an-id');
-			}
-			return true;
-		});
-	}
-
-	public getType() {
-		return super.getType('String');
-	}
-
-	public makeOptional(): ID<undefined> {
-		return new ID(true, false);
-	}
-
-	public makeNullable(): ID<null> {
-		return new ID(false, true);
-	}
-
-	public makeOptionalNullable(): ID<undefined | null> {
-		return new ID(true, true);
-	}
-}
diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts
index f722e74702..6097e3b6ed 100644
--- a/packages/backend/src/remote/activitypub/models/person.ts
+++ b/packages/backend/src/remote/activitypub/models/person.ts
@@ -1,7 +1,6 @@
 import { URL } from 'node:url';
 import promiseLimit from 'promise-limit';
 
-import $, { Context } from 'cafy';
 import config from '@/config/index.js';
 import { registerOrFetchInstanceDoc } from '@/services/register-or-fetch-instance-doc.js';
 import { Note } from '@/models/entities/note.js';
@@ -54,20 +53,33 @@ function validateActor(x: IObject, uri: string): IActor {
 		throw new Error(`invalid Actor type '${x.type}'`);
 	}
 
-	const validate = (name: string, value: any, validater: Context) => {
-		const e = validater.test(value);
-		if (e) throw new Error(`invalid Actor: ${name} ${e.message}`);
-	};
+	if (!(typeof x.id === 'string' && x.id.length > 0)) {
+		throw new Error('invalid Actor: wrong id');
+	}
 
-	validate('id', x.id, $.default.str.min(1));
-	validate('inbox', x.inbox, $.default.str.min(1));
-	validate('preferredUsername', x.preferredUsername, $.default.str.min(1).max(128).match(/^\w([\w-.]*\w)?$/));
+	if (!(typeof x.inbox === 'string' && x.inbox.length > 0)) {
+		throw new Error('invalid Actor: wrong inbox');
+	}
+
+	if (!(typeof x.preferredUsername === 'string' && x.preferredUsername.length > 0 && x.preferredUsername.length <= 128 && /^\w([\w-.]*\w)?$/.test(x.preferredUsername))) {
+		throw new Error('invalid Actor: wrong username');
+	}
 
 	// These fields are only informational, and some AP software allows these
 	// fields to be very long. If they are too long, we cut them off. This way
 	// we can at least see these users and their activities.
-	validate('name', truncate(x.name, nameLength), $.default.optional.nullable.str);
-	validate('summary', truncate(x.summary, summaryLength), $.default.optional.nullable.str);
+	if (x.name) {
+		if (!(typeof x.name === 'string' && x.name.length > 0)) {
+			throw new Error('invalid Actor: wrong name');
+		}
+		x.name = truncate(x.name, nameLength);
+	}
+	if (x.summary) {
+		if (!(typeof x.summary === 'string' && x.summary.length > 0)) {
+			throw new Error('invalid Actor: wrong summary');
+		}
+		x.summary = truncate(x.summary, summaryLength);
+	}
 
 	const idHost = toPuny(new URL(x.id!).hostname);
 	if (idHost !== expectHost) {
diff --git a/packages/backend/yarn.lock b/packages/backend/yarn.lock
index e4421db9d7..981c359dd7 100644
--- a/packages/backend/yarn.lock
+++ b/packages/backend/yarn.lock
@@ -1591,11 +1591,6 @@ cacheable-request@^7.0.2:
     normalize-url "^6.0.1"
     responselike "^2.0.0"
 
-cafy@15.2.1:
-  version "15.2.1"
-  resolved "https://registry.yarnpkg.com/cafy/-/cafy-15.2.1.tgz#5a55eaeb721c604c7dca652f3d555c392e5f995a"
-  integrity sha512-g2zOmFb63p6XcZ/zeMWKYP8YKQYNWnhJmi6K71Ql4EAFTAay31xF0PBPtdBCCfQ0fiETgWTMxKtySAVI/Od6aQ==
-
 call-bind@^1.0.0, call-bind@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"