mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-11 04:44:33 +00:00
fix(acp): harden session lifecycle against flooding
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import type {
|
||||
Agent,
|
||||
AgentSideConnection,
|
||||
@@ -20,6 +19,7 @@ import type {
|
||||
StopReason,
|
||||
} from "@agentclientprotocol/sdk";
|
||||
import { PROTOCOL_VERSION } from "@agentclientprotocol/sdk";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import type { GatewayClient } from "../gateway/client.js";
|
||||
import type { EventFrame } from "../gateway/protocol/index.js";
|
||||
import type { SessionsListResult } from "../gateway/session-utils.js";
|
||||
@@ -50,12 +50,50 @@ type AcpGatewayAgentOptions = AcpServerOptions & {
|
||||
sessionStore?: AcpSessionStore;
|
||||
};
|
||||
|
||||
const SESSION_CREATE_RATE_LIMIT_DEFAULT_MAX_REQUESTS = 120;
|
||||
const SESSION_CREATE_RATE_LIMIT_DEFAULT_WINDOW_MS = 10_000;
|
||||
|
||||
class SessionCreateRateLimiter {
|
||||
private count = 0;
|
||||
private windowStartMs = 0;
|
||||
|
||||
constructor(
|
||||
private readonly maxRequests: number,
|
||||
private readonly windowMs: number,
|
||||
private readonly now: () => number = Date.now,
|
||||
) {}
|
||||
|
||||
consume(): { allowed: boolean; retryAfterMs: number; remaining: number } {
|
||||
const nowMs = this.now();
|
||||
if (nowMs - this.windowStartMs >= this.windowMs) {
|
||||
this.windowStartMs = nowMs;
|
||||
this.count = 0;
|
||||
}
|
||||
|
||||
if (this.count >= this.maxRequests) {
|
||||
return {
|
||||
allowed: false,
|
||||
retryAfterMs: Math.max(0, this.windowStartMs + this.windowMs - nowMs),
|
||||
remaining: 0,
|
||||
};
|
||||
}
|
||||
|
||||
this.count += 1;
|
||||
return {
|
||||
allowed: true,
|
||||
retryAfterMs: 0,
|
||||
remaining: Math.max(0, this.maxRequests - this.count),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class AcpGatewayAgent implements Agent {
|
||||
private connection: AgentSideConnection;
|
||||
private gateway: GatewayClient;
|
||||
private opts: AcpGatewayAgentOptions;
|
||||
private log: (msg: string) => void;
|
||||
private sessionStore: AcpSessionStore;
|
||||
private sessionCreateRateLimiter: SessionCreateRateLimiter;
|
||||
private pendingPrompts = new Map<string, PendingPrompt>();
|
||||
|
||||
constructor(
|
||||
@@ -68,6 +106,16 @@ export class AcpGatewayAgent implements Agent {
|
||||
this.opts = opts;
|
||||
this.log = opts.verbose ? (msg: string) => process.stderr.write(`[acp] ${msg}\n`) : () => {};
|
||||
this.sessionStore = opts.sessionStore ?? defaultAcpSessionStore;
|
||||
this.sessionCreateRateLimiter = new SessionCreateRateLimiter(
|
||||
Math.max(
|
||||
1,
|
||||
opts.sessionCreateRateLimit?.maxRequests ?? SESSION_CREATE_RATE_LIMIT_DEFAULT_MAX_REQUESTS,
|
||||
),
|
||||
Math.max(
|
||||
1_000,
|
||||
opts.sessionCreateRateLimit?.windowMs ?? SESSION_CREATE_RATE_LIMIT_DEFAULT_WINDOW_MS,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
start(): void {
|
||||
@@ -124,6 +172,7 @@ export class AcpGatewayAgent implements Agent {
|
||||
if (params.mcpServers.length > 0) {
|
||||
this.log(`ignoring ${params.mcpServers.length} MCP servers`);
|
||||
}
|
||||
this.enforceSessionCreateRateLimit("newSession");
|
||||
|
||||
const sessionId = randomUUID();
|
||||
const meta = parseSessionMeta(params._meta);
|
||||
@@ -154,6 +203,9 @@ export class AcpGatewayAgent implements Agent {
|
||||
if (params.mcpServers.length > 0) {
|
||||
this.log(`ignoring ${params.mcpServers.length} MCP servers`);
|
||||
}
|
||||
if (!this.sessionStore.getSession(params.sessionId)) {
|
||||
this.enforceSessionCreateRateLimit("loadSession");
|
||||
}
|
||||
|
||||
const meta = parseSessionMeta(params._meta);
|
||||
const sessionKey = await resolveSessionKey({
|
||||
@@ -451,4 +503,14 @@ export class AcpGatewayAgent implements Agent {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private enforceSessionCreateRateLimit(method: "newSession" | "loadSession"): void {
|
||||
const budget = this.sessionCreateRateLimiter.consume();
|
||||
if (budget.allowed) {
|
||||
return;
|
||||
}
|
||||
throw new Error(
|
||||
`ACP session creation rate limit exceeded for ${method}; retry after ${Math.ceil(budget.retryAfterMs / 1_000)}s.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user