@posthog/agent 2.3.74 → 2.3.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@posthog/agent",
3
- "version": "2.3.74",
3
+ "version": "2.3.80",
4
4
  "repository": "https://github.com/PostHog/code",
5
5
  "description": "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
6
6
  "exports": {
@@ -817,7 +817,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
817
817
  cwd,
818
818
  mcpServers,
819
819
  permissionMode,
820
- canUseTool: this.createCanUseTool(sessionId),
820
+ canUseTool: this.createCanUseTool(sessionId, meta?.allowedDomains),
821
821
  logger: this.logger,
822
822
  systemPrompt,
823
823
  userProvidedOptions: meta?.claudeCode?.options,
@@ -978,7 +978,10 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
978
978
  return { sessionId, modes, models, configOptions };
979
979
  }
980
980
 
981
- private createCanUseTool(sessionId: string): CanUseTool {
981
+ private createCanUseTool(
982
+ sessionId: string,
983
+ allowedDomains?: string[],
984
+ ): CanUseTool {
982
985
  return async (toolName, toolInput, { suggestions, toolUseID, signal }) =>
983
986
  canUseTool({
984
987
  session: this.session,
@@ -993,6 +996,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
993
996
  logger: this.logger,
994
997
  updateConfigOption: (configId: string, value: string) =>
995
998
  this.updateConfigOption(configId, value),
999
+ allowedDomains,
996
1000
  });
997
1001
  }
998
1002
 
@@ -49,6 +49,7 @@ interface ToolHandlerContext {
49
49
  fileContentCache: { [key: string]: string };
50
50
  logger: Logger;
51
51
  updateConfigOption: (configId: string, value: string) => Promise<void>;
52
+ allowedDomains?: string[];
52
53
  }
53
54
 
54
55
  async function emitToolDenial(
@@ -422,10 +423,43 @@ function handlePlanFileException(
422
423
  };
423
424
  }
424
425
 
426
+ function extractDomainFromUrl(url: string): string | null {
427
+ try {
428
+ return new URL(url).hostname;
429
+ } catch {
430
+ return null;
431
+ }
432
+ }
433
+
434
+ function isDomainAllowed(hostname: string, allowedDomains: string[]): boolean {
435
+ return allowedDomains.some((pattern) => {
436
+ if (pattern.startsWith("*.")) {
437
+ const suffix = pattern.slice(1); // ".example.com"
438
+ return hostname === pattern.slice(2) || hostname.endsWith(suffix);
439
+ }
440
+ return hostname === pattern;
441
+ });
442
+ }
443
+
425
444
  export async function canUseTool(
426
445
  context: ToolHandlerContext,
427
446
  ): Promise<ToolPermissionResult> {
428
- const { toolName, toolInput, session } = context;
447
+ const { toolName, toolInput, session, allowedDomains } = context;
448
+
449
+ // Enforce domain allowlist for web tools
450
+ if (allowedDomains && allowedDomains.length > 0) {
451
+ if (toolName === "WebFetch" || toolName === "WebSearch") {
452
+ const url = toolInput.url as string | undefined;
453
+ if (url) {
454
+ const hostname = extractDomainFromUrl(url);
455
+ if (hostname && !isDomainAllowed(hostname, allowedDomains)) {
456
+ const message = `Domain "${hostname}" is not in the allowed list: ${allowedDomains.join(", ")}`;
457
+ await emitToolDenial(context, message);
458
+ return { behavior: "deny", message, interrupt: false };
459
+ }
460
+ }
461
+ }
462
+ }
429
463
 
430
464
  if (isToolAllowedForMode(toolName, session.permissionMode)) {
431
465
  return {
@@ -107,6 +107,7 @@ export type NewSessionMeta = {
107
107
  permissionMode?: string;
108
108
  persistence?: { taskId?: string; runId?: string; logUrl?: string };
109
109
  additionalRoots?: string[];
110
+ allowedDomains?: string[];
110
111
  claudeCode?: {
111
112
  options?: Options;
112
113
  };
@@ -707,6 +707,7 @@ export class AgentServer {
707
707
  sessionId: payload.run_id,
708
708
  taskRunId: payload.run_id,
709
709
  systemPrompt: this.buildSessionSystemPrompt(prUrl),
710
+ allowedDomains: this.config.allowedDomains,
710
711
  ...(this.config.claudeCode?.plugins?.length && {
711
712
  claudeCode: {
712
713
  options: {
package/src/server/bin.ts CHANGED
@@ -79,6 +79,10 @@ program
79
79
  "--claudeCodeConfig <json>",
80
80
  "Claude Code config as JSON (systemPrompt, systemPromptAppend, plugins)",
81
81
  )
82
+ .option(
83
+ "--allowedDomains <domains>",
84
+ "Comma-separated list of domains allowed for web tools (WebFetch, WebSearch)",
85
+ )
82
86
  .action(async (options) => {
83
87
  const envResult = envSchema.safeParse(process.env);
84
88
 
@@ -105,6 +109,13 @@ program
105
109
  "--claudeCodeConfig",
106
110
  );
107
111
 
112
+ const allowedDomains = options.allowedDomains
113
+ ? options.allowedDomains
114
+ .split(",")
115
+ .map((d: string) => d.trim())
116
+ .filter(Boolean)
117
+ : undefined;
118
+
108
119
  const server = new AgentServer({
109
120
  port: parseInt(options.port, 10),
110
121
  jwtPublicKey: env.JWT_PUBLIC_KEY,
@@ -118,6 +129,7 @@ program
118
129
  mcpServers,
119
130
  baseBranch: options.baseBranch,
120
131
  claudeCode,
132
+ allowedDomains,
121
133
  });
122
134
 
123
135
  process.on("SIGINT", async () => {
@@ -22,4 +22,5 @@ export interface AgentServerConfig {
22
22
  mcpServers?: RemoteMcpServer[];
23
23
  baseBranch?: string;
24
24
  claudeCode?: ClaudeCodeConfig;
25
+ allowedDomains?: string[];
25
26
  }