Skip to content

Commit fce2d46

Browse files
committed
feat: add allowed-origin flag
1 parent 5abad5d commit fce2d46

File tree

10 files changed

+88
-1
lines changed

10 files changed

+88
-1
lines changed

cmd/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@ func NewCommand(opts ...Option) *Command {
359359
flags.BoolVar(&cmd.cfg.Stdio, "stdio", false, "Listens via MCP STDIO instead of acting as a remote HTTP server.")
360360
flags.BoolVar(&cmd.cfg.DisableReload, "disable-reload", false, "Disables dynamic reloading of tools file.")
361361
flags.BoolVar(&cmd.cfg.UI, "ui", false, "Launches the Toolbox UI web server.")
362+
flags.StringSliceVar(&cmd.cfg.AllowedOrigins, "allowed-origins", []string{"*"}, "Specifies a list of origins permitted to access this server. Default to '*'.")
362363

363364
// wrap RunE command so that we have access to original Command object
364365
cmd.RunE = func(*cobra.Command, []string) error { return run(cmd) }

cmd/root_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ func withDefaults(c server.ServerConfig) server.ServerConfig {
6363
if c.TelemetryServiceName == "" {
6464
c.TelemetryServiceName = "toolbox"
6565
}
66+
if c.AllowedOrigins == nil {
67+
c.AllowedOrigins = []string{"*"}
68+
}
6669
return c
6770
}
6871

@@ -194,6 +197,13 @@ func TestServerConfigFlags(t *testing.T) {
194197
DisableReload: true,
195198
}),
196199
},
200+
{
201+
desc: "allowed origin",
202+
args: []string{"--allowed-origins", "http://foo.com,http://bar.com"},
203+
want: withDefaults(server.ServerConfig{
204+
AllowedOrigins: []string{"http://foo.com", "http://bar.com"},
205+
}),
206+
},
197207
}
198208
for _, tc := range tcs {
199209
t.Run(tc.desc, func(t *testing.T) {

docs/en/how-to/deploy_docker.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@ networks:
6767

6868
```
6969

70+
{{< notice tip >}}
71+
To prevent DNS rebinding attack, use the `--allowed-origins` flag to specify a
72+
list of origins permitted to access the server. E.g. `command: [ "toolbox",
73+
"--tools-file", "/config/tools.yaml", "--address", "0.0.0.0",
74+
"--allowed-origins", "https://foo.bar"]`
75+
{{< /notice >}}
76+
7077
1. Run the following command to bring up the Toolbox and Postgres instance
7178

7279
```bash

docs/en/how-to/deploy_gke.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,12 @@ description: >
188188
path: tools.yaml
189189
```
190190

191+
{{< notice tip >}}
192+
To prevent DNS rebinding attack, use the `--allowed-origins` flag to specify a
193+
list of origins permitted to access the server. E.g. `args: ["--address",
194+
"0.0.0.0", "--allowed-origins", "https://foo.bar"]`
195+
{{< /notice >}}
196+
191197
1. Create the deployment.
192198

193199
```bash

docs/en/how-to/deploy_toolbox.md

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ section.
104104
export IMAGE=us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:latest
105105
```
106106

107-
{{< notice note >}}
107+
{{< notice note >}}
108108
**The `$PORT` Environment Variable**
109109
Google Cloud Run dictates the port your application must listen on by setting
110110
the `$PORT` environment variable inside your container. This value defaults to
@@ -140,6 +140,45 @@ deployment will time out.
140140
# --allow-unauthenticated # https://cloud.google.com/run/docs/authenticating/public#gcloud
141141
```
142142
143+
### Update deployed server to be secure
144+
145+
To prevent DNS rebinding attack, use the `--allowed-origins` flag to specify a
146+
list of origins permitted to access the server. In order to do that, you will
147+
have to re-deploy the cloud run service with the new flag.
148+
149+
1. Set an environment variable to the cloud run url:
150+
151+
```bash
152+
export URL=<cloud run url>
153+
```
154+
155+
2. Redeploy Toolbox:
156+
157+
```bash
158+
gcloud run deploy toolbox \
159+
--image $IMAGE \
160+
--service-account toolbox-identity \
161+
--region us-central1 \
162+
--set-secrets "/app/tools.yaml=tools:latest" \
163+
--args="--tools-file=/app/tools.yaml","--address=0.0.0.0","--port=8080",”--allowed-origins=$URL”
164+
# --allow-unauthenticated # https://cloud.google.com/run/docs/authenticating/public#gcloud
165+
```
166+
167+
If you are using a VPC network, use the command below:
168+
169+
```bash
170+
gcloud run deploy toolbox \
171+
--image $IMAGE \
172+
--service-account toolbox-identity \
173+
--region us-central1 \
174+
--set-secrets "/app/tools.yaml=tools:latest" \
175+
--args="--tools-file=/app/tools.yaml","--address=0.0.0.0","--port=8080",”--allowed-origins=$URL” \
176+
# TODO(dev): update the following to match your VPC if necessary
177+
--network default \
178+
--subnet default
179+
# --allow-unauthenticated # https://cloud.google.com/run/docs/authenticating/public#gcloud
180+
```
181+
143182
## Connecting with Toolbox Client SDK
144183
145184
You can connect to Toolbox Cloud Run instances directly through the SDK.

docs/en/reference/cli.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ description: >
2525
| | `--tools-files` | Multiple file paths specifying tool configurations. Files will be merged. Cannot be used with --prebuilt, --tools-file, or --tools-folder. | |
2626
| | `--tools-folder` | Directory path containing YAML tool configuration files. All .yaml and .yml files in the directory will be loaded and merged. Cannot be used with --prebuilt, --tools-file, or --tools-files. | |
2727
| | `--ui` | Launches the Toolbox UI web server. | |
28+
| | `--allowed-origins` | Specifies a list of origins permitted to access this server. | `*` |
2829
| `-v` | `--version` | version for toolbox | |
2930

3031
## Examples

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ require (
2525
github.com/elastic/go-elasticsearch/v9 v9.2.0
2626
github.com/fsnotify/fsnotify v1.9.0
2727
github.com/go-chi/chi/v5 v5.2.3
28+
github.com/go-chi/cors v1.2.2
2829
github.com/go-chi/httplog/v2 v2.1.1
2930
github.com/go-chi/render v1.0.3
3031
github.com/go-goquery/goquery v1.0.1

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,8 @@ github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9t
860860
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
861861
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
862862
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
863+
github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
864+
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
863865
github.com/go-chi/httplog/v2 v2.1.1 h1:ojojiu4PIaoeJ/qAO4GWUxJqvYUTobeo7zmuHQJAxRk=
864866
github.com/go-chi/httplog/v2 v2.1.1/go.mod h1:/XXdxicJsp4BA5fapgIC3VuTD+z0Z/VzukoB3VDc1YE=
865867
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=

internal/server/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ type ServerConfig struct {
6262
DisableReload bool
6363
// UI indicates if Toolbox UI endpoints (/ui) are available
6464
UI bool
65+
// Specifies a list of origins permitted to access this server.
66+
AllowedOrigins []string
6567
}
6668

6769
type logFormat string

internal/server/server.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@ import (
2020
"io"
2121
"net"
2222
"net/http"
23+
"slices"
2324
"strconv"
2425
"strings"
2526
"sync"
2627
"time"
2728

2829
"github.com/go-chi/chi/v5"
2930
"github.com/go-chi/chi/v5/middleware"
31+
"github.com/go-chi/cors"
3032
"github.com/go-chi/httplog/v2"
3133
"github.com/googleapis/genai-toolbox/internal/auth"
3234
"github.com/googleapis/genai-toolbox/internal/log"
@@ -388,6 +390,7 @@ func NewServer(ctx context.Context, cfg ServerConfig) (*Server, error) {
388390
// set up http serving
389391
r := chi.NewRouter()
390392
r.Use(middleware.Recoverer)
393+
391394
// logging
392395
logLevel, err := log.SeverityToLevel(cfg.LogLevel.String())
393396
if err != nil {
@@ -440,6 +443,21 @@ func NewServer(ctx context.Context, cfg ServerConfig) (*Server, error) {
440443
sseManager: sseManager,
441444
ResourceMgr: resourceManager,
442445
}
446+
447+
// cors
448+
if slices.Contains(cfg.AllowedOrigins, "*") {
449+
s.logger.WarnContext(ctx, "wildcard (`*`) allows all origin to access the resource and is not secure. Use it with cautious for public, non-sensitive data, or during local development. Recommended to use `--allowed-origins` flag to prevent DNS rebinding attacks")
450+
}
451+
corsOpts := cors.Options{
452+
AllowedOrigins: cfg.AllowedOrigins,
453+
AllowedMethods: []string{"GET", "POST", "DELETE", "OPTIONS"},
454+
AllowCredentials: true, // required since Toolbox uses auth headers
455+
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token", "Mcp-Session-Id", "MCP-Protocol-Version"},
456+
ExposedHeaders: []string{"Mcp-Session-Id"}, // headers that are sent to clients
457+
MaxAge: 300, // cache preflight results for 5 minutes
458+
}
459+
r.Use(cors.Handler(corsOpts))
460+
443461
// control plane
444462
apiR, err := apiRouter(s)
445463
if err != nil {

0 commit comments

Comments
 (0)