<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Web-Security · XB Field Notes</title><link>https://hk.xbcatm.com/tags/web-security/</link><description>Illustrated field notes on HTTP, DNS, TLS, Caddy, SSH, releases, and recovery.</description><generator>Hugo</generator><language>en-us</language><atom:link href="https://hk.xbcatm.com/tags/web-security/index.xml" rel="self" type="application/rss+xml"/><item><title>Security headers should describe the actual page</title><link>https://hk.xbcatm.com/notes/security-headers-should-describe-the-actual-page/</link><pubDate>Thu, 23 Jul 2026 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/notes/security-headers-should-describe-the-actual-page/</guid><description>A compact policy is safer when it matches the resources a static page really loads.</description><content:encoded>&lt;p&gt;Security headers are not a substitute for secure code, but they make the browser enforce useful boundaries. The policy should describe the page that is actually served, not a generic template copied from another project.&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/security-layers.detail.en.svg" alt="The actual HTML, local stylesheet and favicon determine CSP resource allowances. Content-Security-Policy enforces; Report-Only reveals violations and needs separate reporting setup. Deliver CSP as a header: frame-ancestors is ignored in meta and has no default-src fallback, form-action needs its own directive, and style-src self alone does not allow inline styles. HSTS is learned from valid HTTPS responses; includeSubDomains at www.example.test covers that host and descendants, not example.test or api.example.test. Check normal, missing and asset responses and verify enforcement in a real browser. No browser test is claimed." width="720" height="3460"&gt;&lt;figcaption&gt;Treat headers as a deployment contract: match actual resources, distinguish enforced and report-only policy, and verify exact host scope and browser behavior.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id="start-from-the-resource-graph"&gt;Start from the resource graph&lt;/h2&gt;
&lt;p&gt;If a site serves HTML, a local stylesheet, a favicon, and no executable JavaScript, a narrow policy can be understandable:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Content-Security-Policy: default-src &amp;#39;self&amp;#39;; base-uri &amp;#39;self&amp;#39;; form-action &amp;#39;none&amp;#39;; frame-ancestors &amp;#39;none&amp;#39;; object-src &amp;#39;none&amp;#39;; script-src &amp;#39;none&amp;#39;; style-src &amp;#39;self&amp;#39;; img-src &amp;#39;self&amp;#39; data:
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;script-src 'none'&lt;/code&gt; choice is only correct while the site does not require executable scripts. Adding search, comments, or an analytics tag changes the resource graph and should trigger a deliberate policy review.&lt;/p&gt;
&lt;h2 id="add-transport-and-framing-protections"&gt;Add transport and framing protections&lt;/h2&gt;
&lt;p&gt;For an HTTPS-only site, the following headers are common starting points:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Strict-Transport-Security: max-age=31536000; includeSubDomains
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;X-Content-Type-Options: nosniff
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;X-Frame-Options: DENY
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Referrer-Policy: strict-origin-when-cross-origin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Only enable HSTS with includeSubDomains when every subdomain is prepared to serve HTTPS. A header is a contract with future deployments, not merely a score in a scanner.&lt;/p&gt;
&lt;h2 id="check-behavior-not-just-presence"&gt;Check behavior, not just presence&lt;/h2&gt;
&lt;p&gt;Request a normal page, a missing page, and a static asset. Error responses should carry the same important security headers as successful responses. Also confirm that compression and caching do not change the content type or expose an internal path.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://hk.xbcatm.com/resources/"&gt;Caddy example&lt;/a&gt; in this site is a starting point, not a universal policy. Adapt the host names, certificate handling, and application routes to your own deployment.&lt;/p&gt;
&lt;p&gt;Deliver the CSP as a response header. &lt;code&gt;frame-ancestors&lt;/code&gt; is ignored in a meta policy and does not inherit &lt;code&gt;default-src&lt;/code&gt;; &lt;code&gt;form-action&lt;/code&gt; also needs its own directive. &lt;code&gt;style-src 'self'&lt;/code&gt; does not allow inline styles by itself, so test the generated theme, including dark mode. Keep &lt;code&gt;data:&lt;/code&gt; in &lt;code&gt;img-src&lt;/code&gt; only when the page actually needs data images.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Content-Security-Policy-Report-Only&lt;/code&gt; can reveal violations during a rollout, but does not enforce the policy; a reporting endpoint needs separate configuration. Verify enforcement in a real browser as well as checking headers. HSTS is learned from valid HTTPS responses; with &lt;code&gt;includeSubDomains&lt;/code&gt; it also applies to descendants of the host that sets it: a policy on &lt;code&gt;www.example.test&lt;/code&gt; does not cover the parent &lt;code&gt;example.test&lt;/code&gt; or sibling &lt;code&gt;api.example.test&lt;/code&gt;. Gradually increase the lifetime after checking that exact scope.&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP"&gt;MDN: Content Security Policy guide&lt;/a&gt;, CSP directive names and syntax, checked 2026-07-23.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Strict-Transport-Security"&gt;MDN: &lt;code&gt;Strict-Transport-Security&lt;/code&gt;&lt;/a&gt;, HSTS directives and deployment cautions, checked 2026-07-23.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://caddyserver.com/docs/caddyfile/directives/header"&gt;Caddy &lt;code&gt;header&lt;/code&gt; directive&lt;/a&gt;, response header manipulation directives, checked 2026-07-23.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.w3.org/TR/CSP3/"&gt;W3C Content Security Policy Level 3&lt;/a&gt;, response-header delivery, directive fallbacks, framing, and report-only policies; current working draft, checked 2026-10-15.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc6797.html"&gt;RFC 6797 — HTTP Strict Transport Security&lt;/a&gt;, valid HTTPS delivery and host/descendant matching in sections 8.1–8.3, checked 2026-10-15.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>HSTS should be rolled out with a recovery plan</title><link>https://hk.xbcatm.com/notes/hsts-should-be-rolled-out-with-a-recovery-plan/</link><pubDate>Tue, 23 Jun 2026 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/notes/hsts-should-be-rolled-out-with-a-recovery-plan/</guid><description>Increase max-age only after HTTPS, redirects, and every covered subdomain are ready.</description><content:encoded>&lt;p&gt;HTTP Strict Transport Security tells a supporting browser to use HTTPS for a host during a cached period. That persistence is the feature and the risk: removing the header does not immediately erase policy already stored by clients.&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/hsts-rollout.detail.en.svg" alt="A rollout rail requires HTTPS and redirect checks, a short example policy and observation of site and renewal before choosing a duration. A domain tree confines includeSubDomains to the sending host’s descendants. Separate preload and recovery panels explain stored-policy limits." width="720" height="2530"&gt;&lt;figcaption&gt;Extend HSTS only after observation. Decide descendant scope and preload separately. Recovery needs valid HTTPS and cannot instantly erase every stored or preloaded policy.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id="verify-the-https-path-first"&gt;Verify the HTTPS path first&lt;/h2&gt;
&lt;p&gt;Before sending HSTS, confirm that the canonical hostname serves a valid certificate and that HTTP redirects to the intended HTTPS URL without depending on user input:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -sS -D - -o /dev/null --max-redirs &lt;span class="m"&gt;0&lt;/span&gt; http://www.example.test/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -sS -D - -o /dev/null https://www.example.test/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Check representative deep routes and error pages. HSTS cannot repair a certificate error because the browser requires a valid HTTPS connection before accepting the policy.&lt;/p&gt;
&lt;h2 id="start-with-a-short-max-age"&gt;Start with a short max-age&lt;/h2&gt;
&lt;p&gt;A staged rollout limits the recovery window while configuration is new:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Strict-Transport-Security: max-age=300
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After observing the site and renewal path, increase the duration deliberately. Record the date, chosen value, and rollback conditions. A final policy might use a year, but the number is a commitment rather than a scanner score.&lt;/p&gt;
&lt;h2 id="treat-includesubdomains-as-a-separate-decision"&gt;Treat includeSubDomains as a separate decision&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;includeSubDomains&lt;/code&gt; extends policy to every subdomain. Inventory names first, including old services, delegated zones, and hostnames used only on internal networks. One HTTP-only descendant is enough to make the option disruptive for clients that received the parent policy.&lt;/p&gt;
&lt;h2 id="preload-is-not-ordinary-header-configuration"&gt;Preload is not ordinary header configuration&lt;/h2&gt;
&lt;p&gt;Browser preload programs can distribute policy outside the normal response cycle. Their requirements and removal delays are stricter than setting a header. Do not request preload merely because a scanner recommends it; confirm long-term ownership of the domain and all covered names.&lt;/p&gt;
&lt;h2 id="verify-the-public-response"&gt;Verify the public response&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -sS -D - -o /dev/null https://www.example.test/ &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt; awk &lt;span class="s1"&gt;&amp;#39;tolower($1) == &amp;#34;strict-transport-security:&amp;#34; { print }&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Test the canonical public edge, not only an origin port. Proxies may add, remove, or duplicate the header.&lt;/p&gt;
&lt;h2 id="know-what-rollback-can-clear"&gt;Know what rollback can clear&lt;/h2&gt;
&lt;p&gt;A valid HTTPS response with &lt;code&gt;max-age=0&lt;/code&gt; clears that host’s learned policy only for clients that receive it. Removing the header or sending it over HTTP does not clear a cached policy. A child cannot opt out of a parent’s still-active &lt;code&gt;includeSubDomains&lt;/code&gt;, and clearing a parent does not delete policies independently learned for children.&lt;/p&gt;
&lt;p&gt;Scope follows the host that sends the header: policy from &lt;code&gt;www.example.test&lt;/code&gt; covers that host and, with &lt;code&gt;includeSubDomains&lt;/code&gt;, its descendants, not the sibling &lt;code&gt;api.example.test&lt;/code&gt; or the parent &lt;code&gt;example.test&lt;/code&gt;. Keep HTTPS working during recovery. Preload removal is a separate browser-update process, not an immediate response-header rollback.&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc6797.html"&gt;RFC 6797 — HTTP Strict Transport Security&lt;/a&gt;, policy processing and scope, checked 2026-10-15.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Strict-Transport-Security"&gt;MDN: &lt;code&gt;Strict-Transport-Security&lt;/code&gt;&lt;/a&gt;, directives and deployment cautions, checked 2026-10-15.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hstspreload.org/"&gt;HSTS preload program&lt;/a&gt;, submission requirements and removal constraints, checked 2026-10-15.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>CAA records should authorize the issuer you actually use</title><link>https://hk.xbcatm.com/notes/caa-records-should-authorize-the-issuer-you-actually-use/</link><pubDate>Wed, 17 Jun 2026 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/notes/caa-records-should-authorize-the-issuer-you-actually-use/</guid><description>Inspect inherited CAA policy before restricting which certificate authorities may issue for a domain.</description><content:encoded>&lt;p&gt;Certification Authority Authorization records let a domain holder state which certificate authorities may issue certificates for the domain. They are useful only when the record matches the real renewal path and the operator understands how policy can be inherited from parent names.&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/caa-policy.detail.en.svg" alt="Follow aliases and walk upward to the first relevant nonempty CAA set, then stop without merging parents. Use the CA issuer identifier; issuewild overrides issue for wildcard requests. CAA constrains issuance, does not perform an HTTPS trust test or revoke existing certificates. Stage renewal changes while the current certificate is healthy." width="720" height="2164"&gt;&lt;figcaption&gt;CAA authorizes issuers at issuance time. Lookup stops at the first relevant nonempty record set; wildcard tags refine the rule. Existing certificates and HTTPS trust are separate boundaries. The displayed issuer is an example.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id="inspect-the-current-policy"&gt;Inspect the current policy&lt;/h2&gt;
&lt;p&gt;Query the exact hostname and its parent names:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; name in www.example.test example.test test&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;== %s\n&amp;#39;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; dig +noall +answer CAA &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These are placeholder names, queried through the configured resolver. An empty answer at the leaf does not necessarily mean there is no applicable policy: lookup follows aliases and stops at the first nonempty relevant CAA RRset while walking upward. It does not merge every parent’s restrictions. Inspect DNS status and authority information too; an empty answer-only display can hide &lt;code&gt;NXDOMAIN&lt;/code&gt; or &lt;code&gt;SERVFAIL&lt;/code&gt;. Compare authoritative answers with recursive results when diagnosing changes.&lt;/p&gt;
&lt;h2 id="match-the-cas-issuer-identifier"&gt;Match the CA’s issuer identifier&lt;/h2&gt;
&lt;p&gt;A record uses tags such as &lt;code&gt;issue&lt;/code&gt; or &lt;code&gt;issuewild&lt;/code&gt; and an issuer-domain value:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;example.test. 3600 IN CAA 0 issue &amp;#34;letsencrypt.org&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The value is an authorization identifier, not the display name from a certificate. Confirm it in the certificate authority&amp;rsquo;s current documentation. Without &lt;code&gt;issuewild&lt;/code&gt;, &lt;code&gt;issue&lt;/code&gt; also governs wildcard issuance. When &lt;code&gt;issuewild&lt;/code&gt; is present, it takes precedence for wildcard requests; multiple records of the same tag grant alternatives rather than requiring every CA. CAA does not select a particular intermediate certificate.&lt;/p&gt;
&lt;h2 id="stage-the-dns-change-before-renewal"&gt;Stage the DNS change before renewal&lt;/h2&gt;
&lt;p&gt;Add or tighten CAA while the current certificate is healthy. Wait through the previous TTL, query authoritative and recursive answers, and run a staging or renewal test supported by the ACME client. Keep a rollback record ready.&lt;/p&gt;
&lt;p&gt;Do not add a restrictive record during an expiry incident unless the issuer path has already been verified. A syntactically correct but wrong policy can turn automatic renewal into a hard failure.&lt;/p&gt;
&lt;h2 id="observe-issuance-failures"&gt;Observe issuance failures&lt;/h2&gt;
&lt;p&gt;CAA checks happen at issuance time, not on every HTTPS request. Changing CAA neither revokes existing certificates nor proves that a historical issuance complied with the policy at that time. Certificate monitoring must therefore cover both current expiry and renewal attempts. Log the effective DNS answer with the ACME failure instead of recording only a generic authorization error.&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc8659.html"&gt;RFC 8659 — DNS Certification Authority Authorization&lt;/a&gt;, CAA lookup and property semantics, checked 2026-10-15.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://letsencrypt.org/docs/caa/"&gt;Let&amp;rsquo;s Encrypt: CAA&lt;/a&gt;, issuer identifiers and operational guidance, checked 2026-10-15.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Cookie scope should be narrower than the application boundary</title><link>https://hk.xbcatm.com/notes/cookie-scope-should-be-narrower-than-the-application-boundary/</link><pubDate>Mon, 15 Jun 2026 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/notes/cookie-scope-should-be-narrower-than-the-application-boundary/</guid><description>Review Domain, Path, Secure, HttpOnly, and SameSite as one browser storage contract.</description><content:encoded>&lt;p&gt;Cookies are attached to requests according to browser rules, not according to the server component that originally set them. A broad &lt;code&gt;Domain&lt;/code&gt; or &lt;code&gt;Path&lt;/code&gt; can expose state to unrelated routes or subdomains, while missing transport and script restrictions enlarge the damage from other failures.&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/cookie-scope.detail.en.svg" alt="Cookie scope begins with host-only versus Domain scope. Path selects request paths but is not authorization. Secure restricts transport without encrypting storage. SameSite controls cross-site context; None needs Secure in modern browsers. HttpOnly blocks document.cookie access but the browser still sends cookies. Supporting browsers require Secure, no Domain and Path=/ for __Host-. Deletion matches the name and domain/path scope, meets prefix and secure-origin requirements, and is separate from server session invalidation. Static-site checks include headers and browser storage." width="720" height="3300"&gt;&lt;figcaption&gt;Review the dimensions together, but keep their roles separate: where cookies are sent, who can read them through JavaScript, and which stored identity a deletion targets.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id="prefer-a-host-only-cookie"&gt;Prefer a host-only cookie&lt;/h2&gt;
&lt;p&gt;When &lt;code&gt;Domain&lt;/code&gt; is omitted, the cookie is scoped to the host that set it. Adding &lt;code&gt;Domain=example.test&lt;/code&gt; allows it to be sent to eligible subdomains too. Use the broader form only when multiple hosts genuinely share the same application and trust boundary.&lt;/p&gt;
&lt;p&gt;Inspect the response without printing real cookie values into logs. Header names are case-insensitive, so redaction must work for lowercase HTTP/2-style names too:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -sS -D - -o /dev/null https://app.example.test/login &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt; awk &lt;span class="s1"&gt;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; tolower($0) ~ /^set-cookie:/ {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; sub(/^[^:]*:[^;]*/, &amp;#34;Set-Cookie: [value redacted]&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; print
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="review-the-attributes-together"&gt;Review the attributes together&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Secure&lt;/code&gt; restricts cookie transmission to secure transport, normally HTTPS; it does not encrypt the stored value.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HttpOnly&lt;/code&gt; prevents JavaScript access through &lt;code&gt;document.cookie&lt;/code&gt;; it does not stop the browser from sending the cookie.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SameSite&lt;/code&gt; influences cross-site requests and must match the application&amp;rsquo;s navigation or embedding needs.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Path&lt;/code&gt; controls when a cookie is sent, but it is not an authorization boundary between applications on the same host.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A cookie using &lt;code&gt;SameSite=None&lt;/code&gt; also needs &lt;code&gt;Secure&lt;/code&gt; in modern browsers. Avoid copying that pair into applications that do not require cross-site use.&lt;/p&gt;
&lt;p&gt;Same-site is broader than same-origin: sibling HTTPS subdomains can be same-site while remaining different origins. &lt;code&gt;SameSite&lt;/code&gt; therefore cannot replace the host boundary or the application’s CSRF defenses. Cookies are also not isolated by port, even with the &lt;code&gt;__Host-&lt;/code&gt; prefix.&lt;/p&gt;
&lt;h2 id="use-prefixes-when-their-constraints-fit"&gt;Use prefixes when their constraints fit&lt;/h2&gt;
&lt;p&gt;Cookie name prefixes such as &lt;code&gt;__Secure-&lt;/code&gt; and &lt;code&gt;__Host-&lt;/code&gt; let supporting browsers enforce additional requirements. A &lt;code&gt;__Host-&lt;/code&gt; cookie must use &lt;code&gt;Secure&lt;/code&gt;, omit &lt;code&gt;Domain&lt;/code&gt;, and use &lt;code&gt;Path=/&lt;/code&gt;, which makes the host-only intention explicit.&lt;/p&gt;
&lt;h2 id="test-deletion-with-the-same-scope"&gt;Test deletion with the same scope&lt;/h2&gt;
&lt;p&gt;Delete the same cookie name and domain/path scope using &lt;code&gt;Max-Age=0&lt;/code&gt; or a past &lt;code&gt;Expires&lt;/code&gt; value; not every attribute is part of its storage identity. Meet the prefix and secure-origin requirements when sending the deletion. Preserve whether &lt;code&gt;Domain&lt;/code&gt; was omitted: expire a host-only cookie without adding &lt;code&gt;Domain&lt;/code&gt;, and specify its original &lt;code&gt;Path&lt;/code&gt;. The current 6265bis draft includes the host-only flag in storage identity, so matching only the displayed domain string is insufficient as a portable deletion recipe. Verify deletion in the target browsers. A logout that clears only one scope can leave another active, and server-side sessions or tokens may also need invalidation.&lt;/p&gt;
&lt;h2 id="keep-static-sites-cookie-free"&gt;Keep static sites cookie-free&lt;/h2&gt;
&lt;p&gt;A genuinely static documentation site normally has no reason to set application cookies. Check representative public responses for &lt;code&gt;Set-Cookie&lt;/code&gt; and inspect browser storage after navigation, including any third-party scripts. A curl header check alone cannot detect JavaScript-set cookies or other browser storage; avoid adding storage the site does not need.&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc6265.html"&gt;RFC 6265 — HTTP State Management Mechanism&lt;/a&gt;, cookie storage and scope, checked 2026-10-15.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie"&gt;MDN: &lt;code&gt;Set-Cookie&lt;/code&gt;&lt;/a&gt;, attributes and cookie prefixes, checked 2026-10-15.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://httpwg.org/http-extensions/draft-ietf-httpbis-rfc6265bis.html"&gt;HTTPWG cookie specification draft&lt;/a&gt;, SameSite and cookie-prefix requirements, checked 2026-10-15.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Content-Type should describe the bytes you actually send</title><link>https://hk.xbcatm.com/notes/content-type-should-describe-the-bytes-you-actually-send/</link><pubDate>Thu, 11 Jun 2026 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/notes/content-type-should-describe-the-bytes-you-actually-send/</guid><description>Check media types at the public edge and use nosniff to make mismatches fail visibly.</description><content:encoded>&lt;p&gt;A static server can return the correct bytes with the wrong &lt;code&gt;Content-Type&lt;/code&gt;. The page may still look acceptable in one browser, while a download, stylesheet, feed reader, or security policy treats the same response differently. The public response header is the contract; the filename is only an input to server configuration.&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/content-type.detail.en.svg" alt="A CSS pathname leads to public-response inspection of status, actual bytes, and Content-Type. Correct CSS and an HTML error page are separate cases. The browser script/style destination boundary applies nosniff; failures require fixing responses, and public CDN/proxy headers must be checked independently of origin headers." width="720" height="2080"&gt;&lt;figcaption&gt;A suffix cannot prove what arrived. Read the public response status, bytes, and media type together; an HTML error page at a CSS path is not a mapping test. Nosniff makes script and style type mismatches visible.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id="inspect-representative-formats"&gt;Inspect representative formats&lt;/h2&gt;
&lt;p&gt;Check one HTML document, stylesheet, SVG, feed, and downloadable script. Include headers but discard the body:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; path in / /assets/site.css /favicon.svg /index.xml /downloads/check-http-headers.sh&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; curl -sS -D - -o /dev/null &lt;span class="s2"&gt;&amp;#34;https://www.example.test&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt; awk -v &lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; BEGIN { print &amp;#34;== &amp;#34; path }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; /^HTTP\// || tolower($1) == &amp;#34;content-type:&amp;#34; || tolower($1) == &amp;#34;x-content-type-options:&amp;#34; { print }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Replace the example asset paths with files that actually exist in the release; an HTML 404 response at an invented CSS path is not a MIME-mapping test. Expected media types depend on the resource, but they should be specific and stable. HTML should normally include a character set. CSS and SVG should not be served as generic binary data. A shell helper offered for download should not be advertised as HTML.&lt;/p&gt;
&lt;p&gt;This loop displays headers; it does not assert success, inspect the discarded bytes, or exercise browser blocking. Confirm the resource against the release artifact and inspect an actual browser load as a stylesheet or script. Opening the same URL as a document tests a different request destination.&lt;/p&gt;
&lt;h2 id="keep-nosniff-consistent"&gt;Keep nosniff consistent&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;X-Content-Type-Options: nosniff&lt;/code&gt; asks supporting browsers to respect the declared type for script and style destinations instead of guessing. For that Fetch check, a script-like destination needs a JavaScript MIME type, and a style destination needs &lt;code&gt;text/css&lt;/code&gt;; a missing or unusable type also fails. This turns a mismatch into an observable failure rather than browser-dependent interpretation.&lt;/p&gt;
&lt;p&gt;The header does not repair a wrong type or make arbitrary content safe. Check the inventory while deploying it, and fix any rejected script/style response rather than removing the protection:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;X-Content-Type-Options: nosniff
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="check-the-server-mapping"&gt;Check the server mapping&lt;/h2&gt;
&lt;p&gt;Static servers usually derive types from extensions. If a new extension is served as &lt;code&gt;application/octet-stream&lt;/code&gt;, decide whether that is intentional before adding a global override. A narrow mapping is easier to review than a rule that changes every response.&lt;/p&gt;
&lt;p&gt;Repeat the checks at the public hostname. A CDN or reverse proxy can replace a correct origin header, and a friendly error page can return HTML for a path whose suffix looks like CSS or JavaScript.&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9110.html"&gt;RFC 9110 — HTTP Semantics&lt;/a&gt;, representation metadata and &lt;code&gt;Content-Type&lt;/code&gt;, checked 2026-10-15.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Type"&gt;MDN: &lt;code&gt;Content-Type&lt;/code&gt;&lt;/a&gt;, media types and charset examples, checked 2026-10-15.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Content-Type-Options"&gt;MDN: &lt;code&gt;X-Content-Type-Options&lt;/code&gt;&lt;/a&gt;, &lt;code&gt;nosniff&lt;/code&gt; behavior, checked 2026-10-15.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fetch.spec.whatwg.org/#should-response-to-request-be-blocked-due-to-nosniff"&gt;WHATWG Fetch Standard&lt;/a&gt;, script-like and style destination blocking, checked 2026-10-15.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>