Skip to content

Commit

Permalink
server: Add nonce to AutoSizer (#7700)
Browse files Browse the repository at this point in the history
`AutoSizer` creates style elements that need a nonce to pass our CSP.

Also add CSP v2 fallback sources for browsers that don't support CSP v3
(`strict-dynamic`).

<!-- Optional: Provide additional context (beyond the PR title). -->

<!-- Optional: link a GitHub issue.
Example: "Fixes #123" will auto-close #123 when the PR is merged. -->

**Related issues**: buildbuddy-io/buildbuddy-internal#3911
  • Loading branch information
fmeum authored Oct 9, 2024
1 parent e10a872 commit 7d55995
Show file tree
Hide file tree
Showing 7 changed files with 22 additions and 14 deletions.
1 change: 1 addition & 0 deletions app/terminal/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ ts_library(
name = "terminal",
srcs = ["terminal.tsx"],
deps = [
"//app/capabilities",
"//app/components/input",
"//app/components/spinner",
"//app/router",
Expand Down
5 changes: 4 additions & 1 deletion app/terminal/terminal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import AutoSizer from "react-virtualized-auto-sizer";
import { Row, ROW_HEIGHT_PX } from "./row";
import { getContent, updatedMatchIndexForSearch, toPlainText, Range, ListData } from "./text";
import router from "../router/router";
import capabilities from "../capabilities/capabilities";

const WRAP_LOCAL_STORAGE_KEY = "terminal-wrap";
const WRAP_LOCAL_STORAGE_VALUE = "wrap";
Expand Down Expand Up @@ -441,7 +442,9 @@ export default class TerminalComponent extends React.Component<TerminalProps, St
{this.props.loading ? (
<div className={`loading ${this.props.lightTheme ? "" : "loading-dark-terminal"}`} />
) : (
<AutoSizer>
// AutoSizer creates style elements that need a nonce to pass a
// strict CSP.
<AutoSizer nonce={capabilities.config.cspNonce || ""}>
{({ height, width }) => (
<FixedSizeList<ListData>
ref={(list) => this.setList(list)}
Expand Down
3 changes: 3 additions & 0 deletions proto/config.proto
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@ message FrontendConfig {

// Whether remote bazel buttons are enabled in the UI.
bool bazel_buttons_enabled = 56;

// The Content Security Policy nonce to use for inline <style> elements.
string csp_nonce = 57;
}

message Region {
Expand Down
3 changes: 2 additions & 1 deletion server/http/csp/csp.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import (
)

// Nonce is the context key type for the per-request CSP nonce.
// The associated value is always of type template.HTMLAttr.
// The associated value is always of type string and should be inserted
// between the double quotes of a nonce = "..." attribute.
type Nonce struct{}

const ReportingEndpoint = "/csp-report"
Expand Down
9 changes: 4 additions & 5 deletions server/http/interceptors/interceptors.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"encoding/base64"
"flag"
"fmt"
"html/template"
"io"
"net"
"net/http"
Expand Down Expand Up @@ -52,7 +51,6 @@ func getContentSecurityPolicyHeaderValue(nonce string) string {
}
return strings.Join([]string{
"default-src 'self'",
"style-src 'self' https://fonts.googleapis.com/css",
// Monaco editor dynamically loads fonts from its CDN.
"font-src 'self' https://fonts.gstatic.com https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/",
// We directly embed profile images from Google accounts and don't control their URLs.
Expand All @@ -68,11 +66,12 @@ func getContentSecurityPolicyHeaderValue(nonce string) string {
"report-to " + contentSecurityPolicyReportingEndpointName,
"report-uri " + csp.ReportingEndpoint,
// libsodium.js requires 'wasm-unsafe-eval' to avoid a fallback to asm.js.
fmt.Sprintf("script-src 'self' 'strict-dynamic' 'wasm-unsafe-eval' 'nonce-%s'", nonce),
fmt.Sprintf("script-src 'nonce-%s' 'strict-dynamic' 'wasm-unsafe-eval' 'self' https: 'unsafe-inline'", nonce),
fmt.Sprintf("style-src 'nonce-%s' 'self' https://fonts.googleapis.com/css", nonce),
}, ";")
}

func setContentSecurityPolicy(h http.Header) template.HTMLAttr {
func setContentSecurityPolicy(h http.Header) string {
nonceBytes := make([]byte, 16)
_, err := rand.Read(nonceBytes)
if err != nil {
Expand All @@ -81,7 +80,7 @@ func setContentSecurityPolicy(h http.Header) template.HTMLAttr {
nonce := base64.StdEncoding.EncodeToString(nonceBytes)
// TODO: Enable this by dropping the "-Report-Only" suffix.
h.Set("Content-Security-Policy-Report-Only", getContentSecurityPolicyHeaderValue(nonce))
return template.HTMLAttr(fmt.Sprintf(`nonce="%s"`, nonce))
return nonce
}

func SetSecurityHeaders(next http.Handler) http.Handler {
Expand Down
7 changes: 4 additions & 3 deletions server/static/static.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,12 @@ type FrontendTemplateData struct {
GaEnabled bool
// Config is the FrontendConfig proto serialized using jsonpb.
Config template.JS
// Nonce is the Content-Security-Policy nonce attribute to use for <script> elements.
Nonce template.HTMLAttr
// Nonce is the Content-Security-Policy nonce value.
Nonce string
}

func serveIndexTemplate(ctx context.Context, env environment.Env, tpl *template.Template, version, jsPath, stylePath, appBundleHash string, w http.ResponseWriter) {
nonce, _ := ctx.Value(csp.Nonce{}).(string)
config := cfgpb.FrontendConfig{
Version: version,
AppBundleHash: appBundleHash,
Expand Down Expand Up @@ -209,14 +210,14 @@ func serveIndexTemplate(ctx context.Context, env environment.Env, tpl *template.
TargetFlakesUiEnabled: *targetFlakesUIEnabled && env.GetOLAPDBHandle() != nil,
CodeEditorV2Enabled: *codeEditorV2Enabled,
BazelButtonsEnabled: *bazelButtonsEnabled,
CspNonce: nonce,
}

configJSON, err := protojson.Marshal(&config)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
nonce, _ := ctx.Value(csp.Nonce{}).(template.HTMLAttr)
w.Header().Set("Content-Type", "text/html")
err = tpl.ExecuteTemplate(w, indexTemplateFilename, &FrontendTemplateData{
StylePath: stylePath,
Expand Down
8 changes: 4 additions & 4 deletions static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@
<meta name="theme-color" content="#212121" />
<meta charset="UTF-8" />

<script {{.Nonce}}>
<script nonce="{{.Nonce}}">
window.buildbuddyConfig = {{.Config}};
</script>

{{if .GaEnabled}}
<script {{.Nonce}} async src="https://www.googletagmanager.com/gtag/js?id=UA-156160991-2"></script>
<script {{.Nonce}}>
<script nonce="{{.Nonce}}" async src="https://www.googletagmanager.com/gtag/js?id=UA-156160991-2"></script>
<script nonce="{{.Nonce}}">
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
Expand All @@ -41,5 +41,5 @@
<div class="loading"></div>
</div>
</body>
<script {{.Nonce}} type="module" src="{{.JsEntryPointPath}}"></script>
<script nonce="{{.Nonce}}" type="module" src="{{.JsEntryPointPath}}"></script>
</html>

0 comments on commit 7d55995

Please sign in to comment.