Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 5c425c6

Browse files
feat: implement HTTP allowed hosts/origins checking (#49)
1 parent e783ff1 commit 5c425c6

File tree

5 files changed

+976
-45
lines changed

5 files changed

+976
-45
lines changed

‎README.md‎

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ Control [Claude Code](https://github.com/anthropics/claude-code), [Goose](https:
44

55
![agentapi-chat](https://github.com/user-attachments/assets/57032c9f-4146-4b66-b219-09e38ab7690d)
66

7-
87
You can use AgentAPI:
98

109
- to build a unified chat interface for coding agents
@@ -54,9 +53,6 @@ You can use AgentAPI:
5453

5554
Run an HTTP server that lets you control an agent. If you'd like to start an agent with additional arguments, pass the full agent command after the `--` flag.
5655

57-
> [!NOTE]
58-
> When using Codex, always specify the agent type explicitly (`agentapi server --type=codex -- codex`), or message formatting may break.
59-
6056
```bash
6157
agentapi server -- claude --allowedTools "Bash(git*) Edit Replace"
6258
```
@@ -68,6 +64,9 @@ agentapi server -- aider --model sonnet --api-key anthropic=sk-ant-apio3-XXX
6864
agentapi server -- goose
6965
```
7066

67+
> [!NOTE]
68+
> When using Codex, always specify the agent type explicitly (`agentapi server --type=codex -- codex`), or message formatting may break.
69+
7170
An OpenAPI schema is available in [openapi.json](openapi.json).
7271

7372
By default, the server runs on port 3284. Additionally, the server exposes the same OpenAPI schema at http://localhost:3284/openapi.json and the available endpoints in a documentation UI at http://localhost:3284/docs.
@@ -79,6 +78,54 @@ There are 4 endpoints:
7978
- GET `/status` - returns the current status of the agent, either "stable" or "running"
8079
- GET `/events` - an SSE stream of events from the agent: message and status updates
8180

81+
#### Allowed hosts
82+
83+
By default, the server only allows requests with the host header set to `localhost`. If you'd like to host AgentAPI elsewhere, you can change this by using the `AGENTAPI_ALLOWED_HOSTS` environment variable or the `--allowed-hosts` flag. Hosts must be hostnames only (no ports); the server ignores the port portion of incoming requests when authorizing.
84+
85+
To allow requests from any host, use `*` as the allowed host.
86+
87+
```bash
88+
agentapi server --allowed-hosts '*' -- claude
89+
```
90+
91+
To allow a specific host, use:
92+
93+
```bash
94+
agentapi server --allowed-hosts 'example.com' -- claude
95+
```
96+
97+
To specify multiple hosts, use a comma-separated list when using the `--allowed-hosts` flag, or a space-separated list when using the `AGENTAPI_ALLOWED_HOSTS` environment variable.
98+
99+
```bash
100+
agentapi server --allowed-hosts 'example.com,example.org' -- claude
101+
# or
102+
AGENTAPI_ALLOWED_HOSTS='example.com example.org' agentapi server -- claude
103+
```
104+
105+
#### Allowed origins
106+
107+
By default, the server allows CORS requests from `http://localhost:3284`, `http://localhost:3000`, and `http://localhost:3001`. If you'd like to change which origins can make cross-origin requests to AgentAPI, you can change this by using the `AGENTAPI_ALLOWED_ORIGINS` environment variable or the `--allowed-origins` flag.
108+
109+
To allow requests from any origin, use `*` as the allowed origin:
110+
111+
```bash
112+
agentapi server --allowed-origins '*' -- claude
113+
```
114+
115+
To allow a specific origin, use:
116+
117+
```bash
118+
agentapi server --allowed-origins 'https://example.com' -- claude
119+
```
120+
121+
To specify multiple origins, use a comma-separated list when using the `--allowed-origins` flag, or a space-separated list when using the `AGENTAPI_ALLOWED_ORIGINS` environment variable. Origins must include the protocol (`http://` or `https://`) and support wildcards (e.g., `https://*.example.com`):
122+
123+
```bash
124+
agentapi server --allowed-origins 'https://example.com,http://localhost:3000' -- claude
125+
# or
126+
AGENTAPI_ALLOWED_ORIGINS='https://example.com http://localhost:3000' agentapi server -- claude
127+
```
128+
82129
### `agentapi attach`
83130

84131
Attach to a running agent's terminal session.

‎cmd/server/server.go‎

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,17 @@ func runServer(ctx context.Context, logger *slog.Logger, argsToPass []string) er
9595
}
9696
}
9797
port := viper.GetInt(FlagPort)
98-
srv := httpapi.NewServer(ctx, httpapi.ServerConfig{
99-
AgentType: agentType,
100-
Process: process,
101-
Port: port,
102-
ChatBasePath: viper.GetString(FlagChatBasePath),
98+
srv, err := httpapi.NewServer(ctx, httpapi.ServerConfig{
99+
AgentType: agentType,
100+
Process: process,
101+
Port: port,
102+
ChatBasePath: viper.GetString(FlagChatBasePath),
103+
AllowedHosts: viper.GetStringSlice(FlagAllowedHosts),
104+
AllowedOrigins: viper.GetStringSlice(FlagAllowedOrigins),
103105
})
106+
if err != nil {
107+
return xerrors.Errorf("failed to create server: %w", err)
108+
}
104109
if printOpenAPI {
105110
fmt.Println(srv.GetOpenAPI())
106111
return nil
@@ -150,12 +155,15 @@ type flagSpec struct {
150155
}
151156

152157
const (
153-
FlagType = "type"
154-
FlagPort = "port"
155-
FlagPrintOpenAPI = "print-openapi"
156-
FlagChatBasePath = "chat-base-path"
157-
FlagTermWidth = "term-width"
158-
FlagTermHeight = "term-height"
158+
FlagType = "type"
159+
FlagPort = "port"
160+
FlagPrintOpenAPI = "print-openapi"
161+
FlagChatBasePath = "chat-base-path"
162+
FlagTermWidth = "term-width"
163+
FlagTermHeight = "term-height"
164+
FlagAllowedHosts = "allowed-hosts"
165+
FlagAllowedOrigins = "allowed-origins"
166+
FlagExit = "exit"
159167
)
160168

161169
func CreateServerCmd() *cobra.Command {
@@ -165,6 +173,10 @@ func CreateServerCmd() *cobra.Command {
165173
Long: fmt.Sprintf("Run the server with the specified agent (one of: %s)", strings.Join(agentNames, ", ")),
166174
Args: cobra.MinimumNArgs(1),
167175
Run: func(cmd *cobra.Command, args []string) {
176+
// The --exit flag is used for testing validation of flags in the test suite
177+
if viper.GetBool(FlagExit) {
178+
return
179+
}
168180
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
169181
ctx := logctx.WithLogger(context.Background(), logger)
170182
if err := runServer(ctx, logger, cmd.Flags().Args()); err != nil {
@@ -181,6 +193,10 @@ func CreateServerCmd() *cobra.Command {
181193
{FlagChatBasePath, "c", "/chat", "Base path for assets and routes used in the static files of the chat interface", "string"},
182194
{FlagTermWidth, "W", uint16(80), "Width of the emulated terminal", "uint16"},
183195
{FlagTermHeight, "H", uint16(1000), "Height of the emulated terminal", "uint16"},
196+
// localhost is the default host for the server. Port is ignored during matching.
197+
{FlagAllowedHosts, "a", []string{"localhost", "127.0.0.1", "[::1]"}, "HTTP allowed hosts (hostnames only, no ports). Use '*' for all, comma-separated list via flag, space-separated list via AGENTAPI_ALLOWED_HOSTS env var", "stringSlice"},
198+
// localhost:3284 is the default origin when you open the chat interface in your browser. localhost:3000 and 3001 are used during development.
199+
{FlagAllowedOrigins, "o", []string{"http://localhost:3284", "http://localhost:3000", "http://localhost:3001"}, "HTTP allowed origins. Use '*' for all, comma-separated list via flag, space-separated list via AGENTAPI_ALLOWED_ORIGINS env var", "stringSlice"},
184200
}
185201

186202
for _, spec := range flagSpecs {
@@ -193,6 +209,8 @@ func CreateServerCmd() *cobra.Command {
193209
serverCmd.Flags().BoolP(spec.name, spec.shorthand, spec.defaultValue.(bool), spec.usage)
194210
case "uint16":
195211
serverCmd.Flags().Uint16P(spec.name, spec.shorthand, spec.defaultValue.(uint16), spec.usage)
212+
case "stringSlice":
213+
serverCmd.Flags().StringSliceP(spec.name, spec.shorthand, spec.defaultValue.([]string), spec.usage)
196214
default:
197215
panic(fmt.Sprintf("unknown flag type: %s", spec.flagType))
198216
}
@@ -201,6 +219,14 @@ func CreateServerCmd() *cobra.Command {
201219
}
202220
}
203221

222+
serverCmd.Flags().Bool(FlagExit, false, "Exit immediately after parsing arguments")
223+
if err := serverCmd.Flags().MarkHidden(FlagExit); err != nil {
224+
panic(fmt.Sprintf("failed to mark flag %s as hidden: %v", FlagExit, err))
225+
}
226+
if err := viper.BindPFlag(FlagExit, serverCmd.Flags().Lookup(FlagExit)); err != nil {
227+
panic(fmt.Sprintf("failed to bind flag %s: %v", FlagExit, err))
228+
}
229+
204230
viper.SetEnvPrefix("AGENTAPI")
205231
viper.AutomaticEnv()
206232
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /