

























@@ -1,3 +1,4 @@
1+import type { IncomingMessage } from "node:http";
12import { createConnection } from "node:net";
23import { afterAll, afterEach, describe, expect, it, vi } from "vitest";
34import {
@@ -37,14 +38,15 @@ vi.mock("./monitor.state.js", async (importOriginal) => {
3738});
38393940import type { RuntimeEnv } from "../runtime-api.js";
41+import { resolveRequestClientIp } from "./monitor-transport-runtime-api.js";
4042import {
4143clearFeishuWebhookRateLimitStateForTest,
4244getFeishuWebhookRateLimitStateSizeForTest,
4345isWebhookRateLimitedForTest,
4446monitorFeishuProvider,
4547stopFeishuMonitor,
4648} from "./monitor.js";
47-import { monitorWebhook } from "./monitor.transport.js";
49+import { buildFeishuWebhookRateLimitKeyForTest, monitorWebhook } from "./monitor.transport.js";
4850import type { ResolvedFeishuAccount } from "./types.js";
49515052async function waitForSlowBodyTimeoutResponse(
@@ -150,6 +152,13 @@ async function waitForOversizedBodyResponse(url: string): Promise<string> {
150152});
151153}
152154155+function resolveTestClientIp(remoteAddress: string | undefined): string | undefined {
156+return resolveRequestClientIp({
157+headers: {},
158+socket: { remoteAddress },
159+} as IncomingMessage);
160+}
161+153162afterEach(() => {
154163clearFeishuWebhookRateLimitStateForTest();
155164stopFeishuMonitor();
@@ -314,6 +323,51 @@ describe("Feishu webhook security hardening", () => {
314323);
315324});
316325326+it("uses one webhook rate-limit key for loopback address-family variants", () => {
327+const base = {
328+accountId: "rate-limit-key",
329+path: "/hook-rate-limit-key",
330+};
331+332+expect([
333+buildFeishuWebhookRateLimitKeyForTest({
334+ ...base,
335+clientIp: resolveTestClientIp("127.0.0.1"),
336+}),
337+buildFeishuWebhookRateLimitKeyForTest({
338+ ...base,
339+clientIp: resolveTestClientIp("127.0.0.42"),
340+}),
341+buildFeishuWebhookRateLimitKeyForTest({
342+ ...base,
343+clientIp: resolveTestClientIp("::ffff:127.0.0.1"),
344+}),
345+buildFeishuWebhookRateLimitKeyForTest({
346+ ...base,
347+clientIp: resolveTestClientIp("::1"),
348+}),
349+]).toEqual([
350+"rate-limit-key:/hook-rate-limit-key:loopback",
351+"rate-limit-key:/hook-rate-limit-key:loopback",
352+"rate-limit-key:/hook-rate-limit-key:loopback",
353+"rate-limit-key:/hook-rate-limit-key:loopback",
354+]);
355+});
356+357+it("keeps non-loopback and unknown webhook rate-limit key suffixes distinct", () => {
358+const base = {
359+accountId: "rate-limit-key",
360+path: "/hook-rate-limit-key",
361+};
362+363+expect(buildFeishuWebhookRateLimitKeyForTest({ ...base, clientIp: "10.0.0.1" })).toBe(
364+"rate-limit-key:/hook-rate-limit-key:10.0.0.1",
365+);
366+expect(buildFeishuWebhookRateLimitKeyForTest(base)).toBe(
367+"rate-limit-key:/hook-rate-limit-key:unknown",
368+);
369+});
370+317371it("caps tracked webhook rate-limit keys to prevent unbounded growth", () => {
318372const now = 1_000_000;
319373for (let i = 0; i < 4_500; i += 1) {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。