Merge pull request '2025.2.0-yumechinokuni.1' () from develop into master

Reviewed-on: 
This commit is contained in:
ゆめ 2025-02-14 06:22:15 -06:00
commit 4c174910e6
7 changed files with 44 additions and 29 deletions

View file

@ -1,3 +1,8 @@
## 2025.2.0-yumechinokuni.1
- Security: Revert miskey-dev/misskey#14897
- Security: AP請求を外部ドメーンにリダイレクトしないように
## 2025.2.0
### General
@ -208,12 +213,6 @@ PgroongaのCWサーチ (github.com/paricafe/misskey#d30db97b59d264450901c1dd8680
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/712)
- Fix: FTT無効時にユーザーリストタイムラインが使用できない問題を修正
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/709)
- Fix: User Webhookテスト機能のMock Payloadを修正
- Fix: アカウント削除のモデレーションログが動作していないのを修正 (#14996)
- Fix: リノートミュートが新規投稿通知に対して作用していなかった問題を修正
- Fix: Inboxの処理で生じるエラーを誤ってActivityとして処理することがある問題を修正
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/730)
- Fix: セキュリティに関する修正
### Misskey.js
- Fix: Stream初期化時、別途WebSocketを指定する場合の型定義を修正

View file

@ -1,6 +1,6 @@
{
"name": "misskey",
"version": "2025.2.0-yumechinokuni.0",
"version": "2025.2.0-yumechinokuni.1",
"codename": "nasubi",
"repository": {
"type": "git",

View file

@ -17,16 +17,16 @@ function getHrefFrom(one: IObject|string): string | undefined {
}
export function assertActivityMatchesUrls(activity: IObject, urls: string[]) {
const idBad = activity.id !== undefined && !urls.includes(activity.id);
let idBad = activity.id !== undefined && !urls.includes(activity.id);
// technically `activity.url` could be an `ApObject = IObject |
// string | (IObject | string)[]`, but if it's a complicated thing
// and the `activity.id` doesn't match, I think we're fine
// rejecting the activity
const urlBad = typeof activity.url === 'string' && !urls.includes(activity.url);
const urlBad = activity.url != undefined && (typeof activity.url !== 'string' || !urls.includes(activity.url));
// both of them have to pass checks, if present
if (idBad || urlBad) {
const hosts = urls.map(u => {
try {
return new URL(u).host;
@ -42,6 +42,7 @@ export function assertActivityMatchesUrls(activity: IObject, urls: string[]) {
throw new Error(`bad Activity: neither id(${activity?.id}) nor url(${JSON.stringify(activity?.url)}) match location(${urls})`);
}
// at least one of the key field must be present
if (activity.id || activity.url) {
return;
}

View file

@ -29,7 +29,6 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js';
import { IActivity } from '@/core/activitypub/type.js';
import { isQuote, isRenote } from '@/misc/is-renote.js';
import * as Acct from '@/misc/acct.js';
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions, FastifyBodyParser } from 'fastify';
import type { FindOptionsWhere } from 'typeorm';
@ -487,16 +486,6 @@ export class ActivityPubServerService {
return;
}
// リモートだったらリダイレクト
if (user.host != null) {
if (user.uri == null || this.utilityService.isSelfHost(user.host)) {
reply.code(500);
return;
}
reply.redirect(user.uri, 301);
return;
}
reply.header('Cache-Control', 'public, max-age=180');
this.setResponseType(request, reply);
return (this.apRendererService.addContext(await this.apRendererService.renderPerson(user as MiLocalUser)));
@ -665,20 +654,19 @@ export class ActivityPubServerService {
const user = await this.usersRepository.findOneBy({
id: userId,
host: IsNull(),
isSuspended: false,
});
return await this.userInfo(request, reply, user);
});
fastify.get<{ Params: { acct: string; } }>('/@:acct', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => {
fastify.get<{ Params: { user: string; } }>('/@:user', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => {
vary(reply.raw, 'Accept');
const acct = Acct.parse(request.params.acct);
const user = await this.usersRepository.findOneBy({
usernameLower: acct.username,
host: acct.host ?? IsNull(),
usernameLower: request.params.user.toLowerCase(),
host: IsNull(),
isSuspended: false,
});

View file

@ -290,6 +290,35 @@ export class ServerService implements OnApplicationShutdown {
done();
});
fastify.addHook('onSend', (request, reply, payload, done) => {
if (reply.statusCode >= 300 && reply.statusCode < 400) {
const isAp = ["application/activity+json", "application/ld+json"].some(type => request.headers.accept?.includes(type));
if (isAp) {
const location = reply.getHeader('location');
// the only acceptable redirect is to our own domain
if (typeof location === 'string') {
// allow http in development
const normalizedLocation = process.env.NODE_ENV !== 'production' ?
location.replace(/^http:\/\//, 'https://') : location;
if ([`https://${this.config.host}/`, `https://${this.config.hostname}/`].some(host => normalizedLocation.startsWith(host))) {
done(null, payload);
return;
}
}
reply.code(406);
reply.removeHeader('location');
done(null, null);
return;
}
}
done(null, payload);
});
// CSP
if (process.env.NODE_ENV === 'production' && !this.config.browserSandboxing.csp?.disable) {
console.debug('cspPrerenderedContent', this.config.cspPrerenderedContent);

View file

@ -230,7 +230,6 @@ describe('Webリソース', () => {
path: path('xxxxxxxxxx'),
type: HTML,
}));
test.todo('HTMLとしてGETできる。(リモートユーザーでもリダイレクトせず)');
});
describe.each([
@ -250,7 +249,6 @@ describe('Webリソース', () => {
path: path('xxxxxxxxxx'),
accept,
}));
test.todo('はオリジナルにリダイレクトされる。(リモートユーザー)');
});
});

View file

@ -1,7 +1,7 @@
{
"type": "module",
"name": "misskey-js",
"version": "2025.2.0-yumechinokuni.0",
"version": "2025.2.0-yumechinokuni.1",
"description": "Misskey SDK for JavaScript",
"license": "MIT",
"main": "./built/index.js",