Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gnoweb): add secure headers by default & timeout configuration #3619

Merged
merged 6 commits into from
Feb 14, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 61 additions & 3 deletions gno.land/cmd/gnoweb/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,19 @@
bind string
faucetURL string
assetsDir string
timeout time.Duration
analytics bool
json bool
html bool
noStrict bool
verbose bool
}

var defaultWebOptions = webCfg{
chainid: "dev",
remote: "127.0.0.1:26657",
bind: ":8888",
timeout: time.Minute,
}

func main() {
Expand Down Expand Up @@ -127,7 +130,14 @@
&c.analytics,
"with-analytics",
defaultWebOptions.analytics,
"nable privacy-first analytics",
"enable privacy-first analytics",
)

fs.BoolVar(
&c.noStrict,
"no-strict",
defaultWebOptions.noStrict,
"allow cross-site resource forgery and disable https enforcement",

Check warning on line 140 in gno.land/cmd/gnoweb/main.go

View check run for this annotation

Codecov / codecov/patch

gno.land/cmd/gnoweb/main.go#L133-L140

Added lines #L133 - L140 were not covered by tests
)

fs.BoolVar(
Expand All @@ -136,6 +146,13 @@
defaultWebOptions.verbose,
"verbose logging mode",
)

fs.DurationVar(
&c.timeout,
"timeout",
defaultWebOptions.timeout,
"set read/write/idle timeout for server connections",
)

Check warning on line 155 in gno.land/cmd/gnoweb/main.go

View check run for this annotation

Codecov / codecov/patch

gno.land/cmd/gnoweb/main.go#L149-L155

Added lines #L149 - L155 were not covered by tests
}

func setupWeb(cfg *webCfg, _ []string, io commands.IO) (func() error, error) {
Expand Down Expand Up @@ -179,18 +196,59 @@

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,
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 {
if err := server.ListenAndServe(); err != nil {
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 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
// 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")
}

Check warning on line 250 in gno.land/cmd/gnoweb/main.go

View check run for this annotation

Codecov / codecov/patch

gno.land/cmd/gnoweb/main.go#L224-L250

Added lines #L224 - L250 were not covered by tests

next.ServeHTTP(w, r)

Check warning on line 252 in gno.land/cmd/gnoweb/main.go

View check run for this annotation

Codecov / codecov/patch

gno.land/cmd/gnoweb/main.go#L252

Added line #L252 was not covered by tests
})
}
3 changes: 2 additions & 1 deletion gno.land/pkg/gnoweb/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion gno.land/pkg/gnoweb/components/ui/icons.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{{ define "ui/icons" }}
<svg xmlns="http://www.w3.org/2000/svg" style="display: none">
<svg xmlns="http://www.w3.org/2000/svg" class="hidden">
<symbol id="ico-search" viewBox="0 0 14 14">
<title>Search</title>
<path
Expand Down
Loading