Repository navigation
fix: フォロワー限定投稿の本人リプライ、リノートを解決できるように - #23
kozakura913 merged 14 commits into
Conversation
develop (2026.10.1 ベース) 追従に伴うコンフリクト解消・適応のみ: - Resolver の DI 化 (TRANSIENT / moduleRef.create) に対応し、 createResolver(resolvedBy) + setUser() 方式へ変更、async 化で await を補完 - ap/show は me を resolver に渡す (upstream と同義) - 絵文字 _misskey_license 周りの解消は develop 側 (normalized.*) を採用 - followingsRepository.exist → exists (TypeORM 準拠)
|
このPRによるapi.jsonの差分 |
⚙️ Backend Diagnostics ReportMemory: After GC(No significant changes) V8 Heap Snapshot Statistics
|
🖥 Frontend Diagnostics Report(No significant changes) Requests by resource type
V8 heap snapshot statistics
📦 Bundle StatsChunk size diff (0 updated, 0 added, 0 removed)
Startup chunk size (0 updated, 0 added, 0 removed)
Startup chunks are the Vite entry for
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
078c0eb to
400c2a2
Compare
|
署名主体のスコープの件は自身のフォロワー限定投稿に返信したり引用した時に署名しないと元投稿が取得できなくなるから署名するのが正しい挙動じゃない? |
4ster1sk
left a comment
There was a problem hiding this comment.
CVE-2026-28431 系のブロック bypass(ap/show + renote)の修正提案です。
ap/show のブロック判定がトップレベルの userId だけを見ているため、非ブロックユーザーのリノート/引用/リプライ経由で、ブロック済み著者の本文が NoteEntityService.pack() の再帰展開で返ります。
ローカルの e2e PoC(未コミット)で確認済み:
viewerがauthorをブロックap/show(original)→NO_SUCH_OBJECTap/show(renote)→200でobject.renote.textに本文
以下の2箇所を renoteUserId / replyUserId も含めて判定するようにしてください。filterVisibleNote 側だけでも PoC は塞げますが、fresh 取得経路も塞ぐため両方の適用を推奨します。
補足: 深い引用チェーンまで塞ぐ場合は renote を再帰的にたどる判定に拡張してください。また、cached note がブロック理由で null になった場合にリモート再取得へ進まない対応(N4)も別途必要です。
結論
実行した確認E2Ecd packages/backend
NODE_ENV=test ./node_modules/.bin/vitest \
--config vitest.config.e2e.ts test/e2e/activitypub-note.ts結果: 28 passed 型チェックcd packages/backend
./node_modules/.bin/tsc6 --noEmit
./node_modules/.bin/tsc6 -p test-federation --noEmit結果:
実装の確認ポイント対象ファイル:
指摘事項1. 高: suspended instance からの private note 取得は塞げていない
// ActivityPubServerService.authenticate() 内など
if (authUser.user.host != null) {
const instance = await this.instancesRepository.findOneBy({
host: this.utilityService.toPuny(authUser.user.host),
});
if (instance != null && instance.suspensionState !== 'none') return null;
}または if (user.host != null && await this.instancesRepository.exists({
where: [
{ host: this.utilityService.toPuny(user.host), quarantineLimited: true },
{ host: this.utilityService.toPuny(user.host), suspensionState: Not('none') },
],
})) return false;ただし public は引き続き匿名で取得できる必要があるため、
|
c1d5d7d to
a07b5ad
Compare
PR #23 — a07b5ad レビュー指摘対象は指定コミット時点のPR差分。以下は独立再検証で採用したテスト上の指摘のみ。Suggested diff は提案であり、ソースには適用していない。 by GPT-6.1 Sol(Medium) 1. [P2] Host不一致テストがHost検証の欠落を検出しない箇所: 問題: 署名者がローカルユーザーなので、Hostチェックを削除しても、 背景・影響: このコミットでローカルユーザーの公開鍵登録が追加されたが、Host不一致テストには引き続き別の拒否条件が残っている。実コードの認証メソッドを抽出し、RSA署名・既存の署名検証ライブラリを使用した隔離テストで、Hostチェック削除の変異が検出されないことを確認した。ローカル署名拒否は別テストで検証済み。 修正方針: 閲覧権限を持つリモートユーザーを署名者にする。正しいHostなら200、不正Hostなら403となる対照も設ける。 Suggested diff: --- a/packages/backend/test/e2e/activitypub-note.ts
+++ b/packages/backend/test/e2e/activitypub-note.ts
@@ -190,5 +190,9 @@
test('followers-only note with mismatched Host header returns 403', async () => {
+ const remote = await createRemoteUser(db);
+ await insertFollowing(db, remote.id, author.id);
const note = await post(author, { text: 'followers host', visibility: 'followers' });
- const res = await signedApGet(`/notes/${note.id}`, followerKeypair, follower.id, 'evil.test');
+ const baseline = await signedApGet(`/notes/${note.id}`, remote, remote.id);
+ assert.strictEqual(baseline.status, 200);
+ const res = await signedApGet(`/notes/${note.id}`, remote, remote.id, 'evil.test');
assert.strictEqual(res.status, 403);
});2. [P2] 指名先でないユーザーのテストが閲覧権限判定に到達しない箇所: 問題: こちらもローカルユーザーの署名なので認証段階で拒否され、 背景・影響: 指名投稿を認証済みの全リモートユーザーに許可してしまう回帰を検出できない。別のfederationテストには非指名先の拒否例があるものの、このHTTPエンドポイントのテスト単体としては意図した条件を検証できていない。 修正方針: 有効なリモート署名を使い、指名先だけを不許可条件にする。リモートの非フォロワーに対するfollowers投稿の拒否例も追加するとよい。 Suggested diff: --- a/packages/backend/test/e2e/activitypub-note.ts
+++ b/packages/backend/test/e2e/activitypub-note.ts
@@ -203,5 +203,6 @@
test('specified note with signed get by non-visible user returns 403', async () => {
+ const remote = await createRemoteUser(db);
const note = await post(author, { text: 'specified hidden', visibility: 'specified', visibleUserIds: [author.id] });
- const res = await signedApGet(`/notes/${note.id}`, followerKeypair, follower.id);
+ const res = await signedApGet(`/notes/${note.id}`, remote, remote.id);
assert.strictEqual(res.status, 403);
});3. [P2] 3インスタンスAnnounceテストが、再取得時の署名ユーザー引き継ぎを検証できない箇所: 問題: フォロー成立後に原投稿を作成しており、通常のCreate配送で原投稿が受信側DBに先に保存され得る。 背景・影響: Announceの最初のURI取得は認証フォールバックを必要とするが、その後の 修正方針: 原投稿をフォロー前に作成する。Announce前に受信側に原投稿がないことも確認し、原投稿の通常配送によるテスト成功を防ぐ。同ファイルの既存2インスタンスAnnounceテストは既にフォロー前に原投稿を作成している。 Suggested diff: --- a/packages/backend/test-federation/test/note.test.ts
+++ b/packages/backend/test-federation/test/note.test.ts
@@ -754,5 +754,3 @@
const authorInB = await resolveRemoteUser('c.test', author.id, follower);
- await follower.client.request('following/create', { userId: authorInB.id });
- await waitForFollowRelation(follower, author, 1);
const noteText = `c.test followers only ${Date.now()}`;
@@ -763,3 +761,11 @@
const noteUri = `https://c.test/notes/${note.id}`;
+ await follower.client.request('following/create', { userId: authorInB.id });
+ await waitForFollowRelation(follower, author, 1);
+ const beforeAnnounce = await follower.client.request('users/notes', {
+ userId: authorInB.id,
+ limit: 100,
+ });
+ assert(!beforeAnnounce.some(n => n.uri === noteUri));
+
// 無関係な a.test のユーザーは取得できない (origin は未認証・権限なしに 403 を返す) |
- Signed Get解決にブロック考慮を追加: findLocalFollowerForSignedGet() で双方向ブロック関係の フォロワーを除外、投稿者と認証ユーザー間にブロック関係があれば403、 サーバーブロック済みホストのユーザーには followers/specified を配信しない - authenticate(): ローカルユーザー (host == null) の署名を拒否、Hostヘッダーを検証、 /notes/:note/activity にも followers/specified のSigned Get認証を適用 - ApInboxService: create/announce の 401/403 時にフォロワーresolverでリトライ、 リトライ結果を announceNote / createNote に引き継ぎ、Signed Get用ローカルフォロワー選定から 凍結ユーザーを除外、選定をSQLで最適化 - ApResolverService: createResolver(resolvedBy) で解決actorを getResolvedBy() により追跡、 ApNoteService: Audience未指定時のpublicフォールバックをSigned Get解決時は適用しない - followers/specified のnote配信 (認証応答) の Cache-Control を private, no-store に - ap/show: ユーザーactorを用いた照会 - test: e2e activitypub-note.ts・federation note.test.ts にSigned Get系テスト追加、block.test.ts 修正 - test: Signed Get の Host 検証と visibleUserIds 判定をリモート署名で検証 (ローカル署名だと host == null で却下され権限判定に届かない)、非フォロワーのリモートユーザーによる followers 投稿 403 を追加、3 インスタンス Announce は原投稿をフォロー前に作成し Announce 前の未取り込みを確認
純正Misskeyには含まれないCheripia独自の挙動に関連する実装 - isAuthorizedForSignedNoteGet に隔離ホスト判定 (instancesRepository.exists quarantineLimited) を追加し、 隔離中のホストには公開投稿 (public / home) だけを返す。 隔離はpush側 (DeliverProcessorService) でだけ効いており、Signed Getというpull経路からは followers/specified が読めていたため pull 側で明示的に拒否する。 併せて authenticate() の isSuspended 重複判定を除去。 - test: 隔離ホストからの signed get e2e (403 / public・homeは200 / 解除後即200)、 federation quarantined-instances.test.ts の順序依存解消と specified / public 対照、 local user署名拒否が host == null 分岐を実際に通る修正、 suspended instance の Signed Get が設計上200の逆方向ピン留め、 「公開投稿」= public / home のコメント明記 - /notes/:note のリモートノート複製で認証後に reply.redirect(note.uri) していたため、 Location にプライベートなorigin URIを露出する非公開302へ Cache-Control (private, no-store) が 適用されていなかった。redirect 前に付与するよう修正。
a07b5ad to
81e81ac
Compare
What
Merge From misskey-dev/misskey#16871
Why
misskey-dev/misskey#15521 参照
Additional info (optional)
人力レビューしてないのでとりあえずDraft
Checklist