# ===== Content Security Policy: only for documents and workers (no locations needed) ===== # 1) Define your CSP once (Jinja: escape double quotes to be safe) set $csp "{{ lookup('applications') | build_csp_header(application_id, lookup('domains'), lookup('tls', application_id, 'protocols.web'), (webserver_csp_whitelist_extra_by_app | default({})).get(application_id, {}), (webserver_csp_hashes_extra_by_app | default({})).get(application_id, {}), DOMAIN_PRIMARY) | replace('\"','\\\"') }}"; # 2) Send CSP ONLY for document responses; also for workers via Sec-Fetch-Dest header_filter_by_lua_block { local ct = ngx.header.content_type or ngx.header["Content-Type"] or "" local dest = ngx.var.http_sec_fetch_dest or "" local lct = ct:lower() local is_html = lct:find("^text/html") or lct:find("^application/xhtml+xml") local is_worker = (dest == "worker") or (dest == "serviceworker") if is_html or is_worker then ngx.header["Content-Security-Policy"] = ngx.var.csp else ngx.header["Content-Security-Policy"] = nil ngx.header["Content-Security-Policy-Report-Only"] = nil end {% include 'roles/sys-svc-proxy/templates/headers/security_headers.lua.j2' %} -- If you'll modify the body later, drop Content-Length on HTML if is_html then ngx.ctx.is_html = true ngx.header.content_length = nil else ngx.ctx.is_html = false {% if (lookup('config', application_id, 'services.tor.enabled', False) | bool) and (lookup('applications') | external_asset_origins(application_id, DOMAIN_PRIMARY)) %} if lct:find("^text/css") and (ngx.var.http_host or ""):sub(-6) == ".onion" then ngx.ctx.is_css = true ngx.header.content_length = nil end {% endif %} end } proxy_hide_header Content-Security-Policy; proxy_hide_header Content-Security-Policy-Report-Only;