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

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 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
62 changes: 60 additions & 2 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 @@ -130,12 +133,26 @@
"nable privacy-first analytics",
gfanton marked this conversation as resolved.
Show resolved Hide resolved
)

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

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

View check run for this annotation

Codecov / codecov/patch

gno.land/cmd/gnoweb/main.go#L136-L142

Added lines #L136 - L142 were not covered by tests
fs.BoolVar(
&c.verbose,
"v",
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'")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I haven't tested it locally yet, but is there a way to host images (for example) on Gnoweb? Because if we restrict access to "img-src" to "self" and there are no tricks for hosting them on Gnoweb, it will force user to hardcode base64 in their markdown.

And I have a feeling that hardcoding binary data into source code is not a great idea. We might need an equivalent to git-lfs. Or a way to host photos on the gno.land domain that relies on simple object storage and is not tied to the blockchain.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a really good point. Discussing a bit with @moul, the plan is to have a whitelist of authorized hosts for images. We can keep allowing any external source for now (for images only) until we implement this whitelist system. I think we also want to deny any embed data: to prevent people from storing raw images on gnoland, but I'm unsure of applying this in this PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, got it. Looks good 👍


// 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
Loading