From e1861e7b91b1b0d29fc639ad991215cef28b35b6 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Tue, 28 Jan 2025 11:08:53 +0100 Subject: [PATCH 1/4] feat: add secure headers to gnoweb by default Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/cmd/gnoweb/main.go | 48 +++++++++++++++++++++++++++++++++++- gno.land/pkg/gnoweb/Makefile | 3 ++- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/gno.land/cmd/gnoweb/main.go b/gno.land/cmd/gnoweb/main.go index 8c0df00aa35..656dc9337f3 100644 --- a/gno.land/cmd/gnoweb/main.go +++ b/gno.land/cmd/gnoweb/main.go @@ -26,6 +26,7 @@ type webCfg struct { analytics bool json bool html bool + noStrict bool verbose bool } @@ -130,6 +131,13 @@ func (c *webCfg) RegisterFlags(fs *flag.FlagSet) { "nable privacy-first analytics", ) + fs.BoolVar( + &c.noStrict, + "no-strict", + defaultWebOptions.noStrict, + "allow cross-site resource forgery and disable https enforcement", + ) + fs.BoolVar( &c.verbose, "v", @@ -179,9 +187,12 @@ func setupWeb(cfg *webCfg, _ []string, io commands.IO) (func() error, error) { logger.Info("Running", "listener", bindaddr.String()) + // Setup security headers + secureHandler := SecureHeadersMiddleware(app, !cfg.noStrict) + // Setup server server := &http.Server{ - Handler: app, + Handler: secureHandler, Addr: bindaddr.String(), ReadHeaderTimeout: 60 * time.Second, } @@ -191,6 +202,41 @@ func setupWeb(cfg *webCfg, _ []string, io commands.IO) (func() error, error) { logger.Error("HTTP server stopped", "error", err) return commands.ExitCodeError(1) } + return nil }, nil } + +func SecureHeadersMiddleware(next http.Handler, strict bool) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Prevent MIME type sniffing by browsers. This ensures that the browser + // does not interpret files as a different MIME type than declared. + w.Header().Set("X-Content-Type-Options", "nosniff") + + // Prevent the page from being embedded in an iframe. This mitigates + // clickjacking attacks by ensuring the page cannot be loaded in a frame. + w.Header().Set("X-Frame-Options", "DENY") + + // Control the amount of referrer information sent in the Referer header. + // 'no-referrer' ensures that no referrer information is sent, which + // enhances privacy and prevents leakage of sensitive URLs. + w.Header().Set("Referrer-Policy", "no-referrer") + + // In `strict` mode, prevent cross-site ressources and enforce https + if strict { + // Define a Content Security Policy (CSP) to restrict the sources of + // scripts, styles, images, and other resources. This helps prevent + // cross-site scripting (XSS) and other code injection attacks. + // - 'self' allows resources from the same origin. + // - 'data:' allows inline images (e.g., base64-encoded images). + w.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'") + + // Enforce HTTPS by telling browsers to only access the site over HTTPS + // for a specified duration (1 year in this case). This also applies to + // subdomains and allows preloading into the browser's HSTS list. + w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload") + } + + next.ServeHTTP(w, r) + }) +} diff --git a/gno.land/pkg/gnoweb/Makefile b/gno.land/pkg/gnoweb/Makefile index c8d662ec3b5..be7c2c2a3a2 100644 --- a/gno.land/pkg/gnoweb/Makefile +++ b/gno.land/pkg/gnoweb/Makefile @@ -80,7 +80,8 @@ dev: # Go server in development mode dev.gnoweb: generate $(run_reflex) -s -r '.*\.(go|html)' -- \ - go run ../../cmd/gnoweb -assets-dir=${PUBLIC_DIR} -chainid=${CHAIN_ID} -remote=${DEV_REMOTE} \ + go run ../../cmd/gnoweb -no-strict -assets-dir=${PUBLIC_DIR} -chainid=${CHAIN_ID} -remote=${DEV_REMOTE} \ + 2>&1 | $(run_logname) gnoweb # Tailwind CSS in development mode From e5fe991db4a86b459a7ec401640b9eeee9b1fc9e Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Tue, 28 Jan 2025 11:10:07 +0100 Subject: [PATCH 2/4] feat: add timeout configuration Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/cmd/gnoweb/main.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/gno.land/cmd/gnoweb/main.go b/gno.land/cmd/gnoweb/main.go index 656dc9337f3..a9dae45649e 100644 --- a/gno.land/cmd/gnoweb/main.go +++ b/gno.land/cmd/gnoweb/main.go @@ -23,6 +23,7 @@ type webCfg struct { bind string faucetURL string assetsDir string + timeout time.Duration analytics bool json bool html bool @@ -34,6 +35,7 @@ var defaultWebOptions = webCfg{ chainid: "dev", remote: "127.0.0.1:26657", bind: ":8888", + timeout: time.Minute, } func main() { @@ -144,6 +146,13 @@ func (c *webCfg) RegisterFlags(fs *flag.FlagSet) { defaultWebOptions.verbose, "verbose logging mode", ) + + fs.DurationVar( + &c.timeout, + "timeout", + defaultWebOptions.timeout, + "set read/write/idle timeout for server connections", + ) } func setupWeb(cfg *webCfg, _ []string, io commands.IO) (func() error, error) { @@ -194,7 +203,10 @@ func setupWeb(cfg *webCfg, _ []string, io commands.IO) (func() error, error) { server := &http.Server{ Handler: secureHandler, Addr: bindaddr.String(), - ReadHeaderTimeout: 60 * time.Second, + ReadTimeout: cfg.timeout, // Time to read the request + WriteTimeout: cfg.timeout, // Time to write the entire response + IdleTimeout: cfg.timeout, // Time to keep idle connections open + ReadHeaderTimeout: time.Minute, // Time to read request headers } return func() error { From 84ebdba0ad22204de7a144eb4e427f87e3d89ee7 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Tue, 28 Jan 2025 11:17:16 +0100 Subject: [PATCH 3/4] chore: missing typo Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/cmd/gnoweb/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gno.land/cmd/gnoweb/main.go b/gno.land/cmd/gnoweb/main.go index a9dae45649e..7d054837a8f 100644 --- a/gno.land/cmd/gnoweb/main.go +++ b/gno.land/cmd/gnoweb/main.go @@ -234,7 +234,7 @@ func SecureHeadersMiddleware(next http.Handler, strict bool) http.Handler { // enhances privacy and prevents leakage of sensitive URLs. w.Header().Set("Referrer-Policy", "no-referrer") - // In `strict` mode, prevent cross-site ressources and enforce https + // In `strict` mode, prevent cross-site ressources forgery and enforce https if strict { // Define a Content Security Policy (CSP) to restrict the sources of // scripts, styles, images, and other resources. This helps prevent From 432d8890a9cd2753c7a5e995d3543052a8fdb13b Mon Sep 17 00:00:00 2001 From: Guilhem Fanton <8671905+gfanton@users.noreply.github.com> Date: Tue, 28 Jan 2025 14:28:24 +0100 Subject: [PATCH 4/4] chore: typo Co-authored-by: Antoine Eddi <5222525+aeddi@users.noreply.github.com> --- gno.land/cmd/gnoweb/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gno.land/cmd/gnoweb/main.go b/gno.land/cmd/gnoweb/main.go index 7d054837a8f..a862147308c 100644 --- a/gno.land/cmd/gnoweb/main.go +++ b/gno.land/cmd/gnoweb/main.go @@ -130,7 +130,7 @@ func (c *webCfg) RegisterFlags(fs *flag.FlagSet) { &c.analytics, "with-analytics", defaultWebOptions.analytics, - "nable privacy-first analytics", + "enable privacy-first analytics", ) fs.BoolVar(