<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[hackermr]]></title><description><![CDATA[hackermr]]></description><link>https://hackerme.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Sat, 07 Nov 2026 11:26:49 GMT</lastBuildDate><atom:link href="https://hackerme.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[From Report-Only to Enforced CSP: A Practical Rollout]]></title><description><![CDATA[How to move Content-Security-Policy from logging violations to blocking them without treating a soft header as done.]]></description><link>https://hackerme.hashnode.dev/from-report-only-to-enforced-csp-a-practical-rollout</link><guid isPermaLink="true">https://hackerme.hashnode.dev/from-report-only-to-enforced-csp-a-practical-rollout</guid><category><![CDATA[cybersecurity]]></category><category><![CDATA[Web Security]]></category><category><![CDATA[Devops]]></category><category><![CDATA[owasp]]></category><category><![CDATA[Security]]></category><dc:creator><![CDATA[HackerMe]]></dc:creator><pubDate>Thu, 05 Nov 2026 13:23:23 GMT</pubDate><content:encoded><![CDATA[<p>Scanners keep reporting “Missing Content-Security-Policy header.” Adding any CSP line is not the hard part. Shipping a policy that blocks bad scripts without breaking your own app is.</p>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP">Content Security Policy</a> is a browser contract: which scripts, styles, frames, and other resources this document may load. <a href="https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html">OWASP</a> is clear that CSP sits on top of sanitization and safe coding; it does not replace them. This piece is a step plan from Report-Only to enforcement.</p>
<h2>Step 0 — Know what you serve today</h2>
<pre><code class="language-bash">curl -sL -D csp-headers.txt -o csp-body.html -w 'final_url=%{url_effective}\n' https://example.com/
grep -i '^content-security-policy:' csp-headers.txt
grep -i '^content-security-policy-report-only:' csp-headers.txt
</code></pre>
<ul>
<li>No <code>Content-Security-Policy:</code> line: no enforced policy was found in the response headers; also check the HTML for a <code>&lt;meta http-equiv="Content-Security-Policy"&gt;</code> tag.</li>
<li>Only Report-Only: useful for learning; it does not stop a bad script.</li>
<li>Prefer the HTTP header over meta when you need <code>frame-ancestors</code> or reporting (<a href="https://wstg.owasp.org/latest/4-Web_Application_Security_Testing/02-Configuration_and_Deployment_Management/12-Content_Security_Policy/">WSTG-CONF-12</a>).</li>
</ul>
<p>Repeat on <code>/login</code> or <code>/account</code> if you are allowed to. Policies often differ by route.</p>
<h2>Step 1 — Draft a candidate policy</h2>
<p>Start from OWASP’s basic same-origin example only as a starting point (not Strict CSP, not a ready setting for every site):</p>
<pre><code class="language-http">Content-Security-Policy-Report-Only: default-src 'self'; frame-ancestors 'self'; form-action 'self';
</code></pre>
<p>A missing <code>object-src</code> alone is not proof of weakness — it can fall back to <code>default-src</code>. When you tighten later, strict examples often add <code>object-src 'none'</code> and <code>base-uri 'none'</code>, and move <code>script-src</code> to nonces or hashes.</p>
<h2>Step 2 — Ship Report-Only on staging, then production</h2>
<p>Deploy the candidate as <code>Content-Security-Policy-Report-Only</code>. Review what would be blocked in the browser console and at your reporting endpoint. Report-Only itself does not break the site; it surfaces violations that an enforced header would stop.</p>
<p>Allow or refactor legitimate assets (analytics, fonts, embeds) deliberately. Do not widen <code>script-src</code> to <code>*</code> just to silence noise.</p>
<h2>Step 3 — Enforce the same policy</h2>
<p>When the report stream is quiet for the paths that matter, move the same directives to <code>Content-Security-Policy</code>. MDN notes both headers can coexist: keep enforcing one policy while reporting on a stricter candidate.</p>
<p>Do not park forever in Report-Only. A year of violation logs is not a browser block.</p>
<h2>Step 4 — Re-check after every frontend change</h2>
<p>New packages and third-party scripts often reintroduce <code>'unsafe-inline'</code> or new hosts. Re-run the curl check on homepage and session pages after releases.</p>
<h2>Soft policies still fail the real goal</h2>
<p>If <code>script-src</code> allows <code>'unsafe-inline'</code> without a nonce or hash, or allows <code>'unsafe-eval'</code> / bare <code>*</code>, you may clear a “missing header” finding and still leave the XSS path open. Aim for nonce- or hash-based strict CSP rather than a huge host allowlist.</p>
<h2>Limits</h2>
<p>A clean CSP check does not prove the app is free of XSS or that every dependency is trustworthy. Outside tools only see returned headers and bodies.</p>
<h2>About us</h2>
<p>We build HackerMe, an automated website security scanner by Agura / OMC Group. Security headers are part of every website scan; a sample finding is “Missing Content-Security-Policy header.” Deeper CSP analysis can check policy quality on Standard and higher scan types. HackerMe offers a free scan on a domain you own or have explicit permission to test: <a href="https://brandingme.ai/app/r/Cxr3PSelcezVc6zxoLFyzw">https://brandingme.ai/app/r/Cxr3PSelcezVc6zxoLFyzw</a></p>
<h3>Sources</h3>
<ul>
<li>MDN CSP: <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP">https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP</a></li>
<li>OWASP CSP Cheat Sheet: <a href="https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html">https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html</a></li>
<li>OWASP WSTG-CONF-12: <a href="https://wstg.owasp.org/latest/4-Web_Application_Security_Testing/02-Configuration_and_Deployment_Management/12-Content_Security_Policy/">https://wstg.owasp.org/latest/4-Web_Application_Security_Testing/02-Configuration_and_Deployment_Management/12-Content_Security_Policy/</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Where Does a Stranger Report a Bug on Your Site? security.txt in 6 Checks]]></title><description><![CDATA[RFC 9116 gives researchers one predictable place to find your security contact. How to write the file, check it, keep it from expiring, and what it does…]]></description><link>https://hackerme.hashnode.dev/security-txt-rfc-9116-checks</link><guid isPermaLink="true">https://hackerme.hashnode.dev/security-txt-rfc-9116-checks</guid><category><![CDATA[Security]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Devops]]></category><category><![CDATA[best practices]]></category><category><![CDATA[owasp]]></category><dc:creator><![CDATA[HackerMe]]></dc:creator><pubDate>Wed, 04 Nov 2026 17:27:02 GMT</pubDate><content:encoded><![CDATA[<p><em>Written by the HackerMe team (Agura); we build HackerMe, an automated website security scanner. The first four checks only read a public file.</em></p>
<p>Someone finds a problem on your site: an exposed file, a misconfigured header, a login bug. They want to tell you. Where do they go? A contact form that routes to sales? A generic info@ inbox? A social media account?</p>
<p>RFC 9116 answers that with a small text file at a fixed location: <code>/.well-known/security.txt</code>. It does not fix anything by itself. It makes the first step of a responsible report predictable. Here are six checks for setting it up and keeping it honest.</p>
<h2>1. Is the file where clients will look? (safe on any public site)</h2>
<pre><code class="language-bash">curl -s -D headers.txt -o security.txt https://example.com/.well-known/security.txt
head -1 headers.txt; grep -i '^content-type' headers.txt; head security.txt
</code></pre>
<p>You want a <code>200</code>, a <code>text/plain</code> content type, and the file over HTTPS; RFC 9116 places it under <code>/.well-known/</code> and requires HTTPS. Look at the body too: some sites answer every path with <code>200</code> and an HTML error page, or redirect to the homepage, which is not a security.txt.</p>
<h2>2. Does it have the two required fields? (safe on any public site)</h2>
<p>RFC 9116 requires <code>Contact</code> and <code>Expires</code>. A minimal file:</p>
<pre><code class="language-text">Contact: mailto:security@example.com
Expires: 2027-10-31T22:00:00.000Z
Preferred-Languages: en, he
Canonical: https://example.com/.well-known/security.txt
Policy: https://example.com/security-policy
</code></pre>
<p>At least one <code>Contact</code> (a <code>mailto:</code>, <code>tel:</code> or <code>https:</code> URI) and exactly one <code>Expires</code>: the RFC says <code>Expires</code> must always be present and must not appear more than once. A file with only a contact, or with several expiry dates, needs fixing. Open each URI in the optional fields as well; nothing checks that they lead somewhere real.</p>
<h2>3. Has it expired? (safe on any public site)</h2>
<p>The RFC recommends an <code>Expires</code> value less than a year into the future, so stale files are noticeable. Compare the value with today's date.</p>
<p>A date in the past tells researchers the information may be out of date. Put the renewal in the same calendar as your domain and certificate renewals.</p>
<h2>4. Does Canonical match where it is served? (safe on any public site)</h2>
<p><code>Canonical</code> is optional. If you use it (useful when you sign the file or serve it on several hosts), it lists the URIs where this exact file lives, and the RFC says that when <code>Canonical</code> fields are present and none of them matches the URI the file was retrieved from, the contents should not be trusted. A file with no <code>Canonical</code> field is still valid; a <code>Canonical</code> pointing at a different domain, a staging host or <code>http://</code> is the problem.</p>
<p>It also does not say anything about subdomains. Per the RFC, a file applies to the host it is served from; <code>shop.example.com</code> needs its own file or a redirect you have thought through.</p>
<h2>5. Signed or not? (maintainer check)</h2>
<p>The RFC recommends signing the file with an OpenPGP cleartext signature. It is optional. If you sign, publish the signing key somewhere you control and re-sign every time you edit the file. Do not treat the <code>Encryption</code> field as the signing key: it points to a key for encrypting reports to you, and RFC 9116 warns researchers not to assume that key is the one used for the signature. A signature that no longer verifies is worse than none, because it trains readers to ignore failures.</p>
<h2>6. Does a message to the contact reach a person? (maintainer check)</h2>
<p>This is the check no scanner can do for you. Send a test message to the listed address from outside your organization and confirm:</p>
<ul>
<li>it is not caught by a spam filter,</li>
<li>it reaches more than one person,</li>
<li>someone knows what to do next.</li>
</ul>
<p>The OWASP Vulnerability Disclosure Cheat Sheet recommends a clear process for receiving and handling reports. The file only helps if that process exists behind it.</p>
<h2>What security.txt does not do</h2>
<ul>
<li>It does not make a site safer. It is a contact card.</li>
<li>It does not grant permission to test your site unless your policy says so. If you have rules for researchers, put them on the <code>Policy</code> page.</li>
<li>A valid file is not evidence that reports are handled well.</li>
</ul>
<h2>About us</h2>
<p>HackerMe runs automated checks from the outside on a domain you own or have explicit permission to test. It sees only what your server returns, so treat its results as a starting point. <a href="https://brandingme.ai/app/r/1s5brArmi4E9vtT-nWbcZg">https://brandingme.ai/app/r/1s5brArmi4E9vtT-nWbcZg</a></p>
<h3>Sources</h3>
<ul>
<li>IETF, RFC 9116, A File Format to Aid in Security Vulnerability Disclosure: <a href="https://www.rfc-editor.org/rfc/rfc9116">https://www.rfc-editor.org/rfc/rfc9116</a></li>
<li>securitytxt.org (project site by the RFC authors): <a href="https://securitytxt.org/">https://securitytxt.org/</a></li>
<li>OWASP, Vulnerability Disclosure Cheat Sheet: <a href="https://cheatsheetseries.owasp.org/cheatsheets/Vulnerability_Disclosure_Cheat_Sheet.html">https://cheatsheetseries.owasp.org/cheatsheets/Vulnerability_Disclosure_Cheat_Sheet.html</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Session Cookies: 7 Checks for Secure, HttpOnly, SameSite and Logout, and What a Pass Doesn't Prove]]></title><description><![CDATA[A practical way to review session cookie attributes and session lifecycle on your own web app, with what each result means and where it stops.]]></description><link>https://hackerme.hashnode.dev/session-cookies-7-checks-for-secure-httponly-samesite-and-logout-and-what-a-pass-doesn-t-prove</link><guid isPermaLink="true">https://hackerme.hashnode.dev/session-cookies-7-checks-for-secure-httponly-samesite-and-logout-and-what-a-pass-doesn-t-prove</guid><category><![CDATA[Security]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[cookies]]></category><category><![CDATA[authentication]]></category><category><![CDATA[owasp]]></category><dc:creator><![CDATA[HackerMe]]></dc:creator><pubDate>Tue, 03 Nov 2026 06:23:01 GMT</pubDate><content:encoded><![CDATA[<p><em>Published by the HackerMe team (Agura), the makers of HackerMe, a free automated website security scanner. The checks below are vendor-neutral. Each one is labeled:</em> <em><strong>safe on any public site</strong></em> <em>(it only reads headers the site sends to every visitor),</em> <em><strong>your own sites only</strong></em> <em>(it uses a test account, so run it only on systems you own or have written permission to test), or</em> <em><strong>maintainer check</strong></em> <em>(it needs access to the code or server configuration).</em></p>
<p>A session cookie is often the only thing that proves a request comes from a logged-in user. Its attributes decide when the browser sends it and whether scripts can read it, and the session lifecycle decides how long it stays valid. This post covers seven checks, with what you should see, what counts as a finding and what a pass does not prove. Background: <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie">MDN: Set-Cookie</a>, the <a href="https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html">OWASP Session Management Cheat Sheet</a> and <a href="https://www.rfc-editor.org/rfc/rfc6265">RFC 6265</a>.</p>
<h2>1. See what the site sets (safe on any public site)</h2>
<pre><code class="language-bash">curl -sI https://example.com/ | grep -i '^set-cookie'
</code></pre>
<p>Session cookies are usually issued at login, so on your own app also inspect the login response in the browser dev tools (Network tab).</p>
<ul>
<li><strong>Doesn't prove:</strong> anything about cookies set later or by JavaScript. One response is a sample.</li>
</ul>
<h2>2. Secure (safe on any public site)</h2>
<p>RFC 6265 says a cookie with <code>Secure</code> is sent only over channels the browser considers secure, which in practice means HTTPS.</p>
<ul>
<li><strong>Expected:</strong> every session or authentication cookie has <code>Secure</code>.</li>
<li><strong>Finding:</strong> a session cookie without it. OWASP also warns against switching a session between HTTP and HTTPS, which exposes the session ID.</li>
<li><strong>Doesn't prove:</strong> that the whole site is served over HTTPS. Check redirects and HSTS separately.</li>
</ul>
<h2>3. HttpOnly (safe on any public site)</h2>
<p><code>HttpOnly</code> stops scripts from reading the cookie, for example through <code>document.cookie</code> (MDN).</p>
<ul>
<li><strong>Expected:</strong> session cookies have <code>HttpOnly</code>. Cookies that front-end code must read should not hold the session ID.</li>
<li><strong>Finding:</strong> a session cookie readable from JavaScript.</li>
<li><strong>Doesn't prove:</strong> that the site is safe from XSS. MDN notes that an <code>HttpOnly</code> cookie is still sent with requests made by JavaScript, so a script running on the page can still act as the user.</li>
</ul>
<h2>4. SameSite (safe on any public site, plus maintainer check)</h2>
<p><code>SameSite</code> controls whether the cookie goes out with cross-site requests. <code>Strict</code> sends it only for same-site requests. <code>Lax</code> also allows top-level navigations that use safe methods such as <code>GET</code>. <code>None</code> sends it everywhere and requires <code>Secure</code>.</p>
<ul>
<li><strong>Expected:</strong> an explicit <code>SameSite</code> value that matches how the app is used. MDN notes that only some browsers default to <code>Lax</code> when the attribute is missing, so don't rely on the default.</li>
<li><strong>Finding:</strong> no <code>SameSite</code>, or <code>SameSite=None</code> on a session cookie with no clear cross-site need.</li>
<li><strong>Doesn't prove:</strong> CSRF protection. The <a href="https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html">OWASP CSRF Prevention Cheat Sheet</a> points out that <code>Lax</code> does not stop a state-changing action reachable by <code>GET</code>, and that <code>SameSite</code> is scoped to the site, not the origin. Checking for state-changing <code>GET</code> endpoints is a maintainer check.</li>
</ul>
<h2>5. Scope: Domain, Path and prefixes (safe on any public site)</h2>
<p>MDN explains that omitting <code>Domain</code> makes a host-only cookie, while setting it shares the cookie with every subdomain.</p>
<ul>
<li><strong>Expected:</strong> no <code>Domain</code> attribute unless subdomains truly need the session. A <code>__Host-</code> prefix enforces this: the browser accepts such a cookie only with <code>Secure</code>, <code>Path=/</code> and no <code>Domain</code>.</li>
<li><strong>Finding:</strong> <code>Domain=example.com</code> on a session cookie when some subdomains are run by third parties.</li>
</ul>
<h2>6. Session ID renewed at login (your own sites only)</h2>
<p>OWASP says the session ID "must be renewed or regenerated" after any privilege level change, most commonly at login. Using a test account, note the session cookie value on the login page, log in, and compare.</p>
<ul>
<li><strong>Expected:</strong> a new value after login.</li>
<li><strong>Finding:</strong> the same value before and after login, which can make session fixation possible. See <a href="https://wstg.owasp.org/v4.2/4-Web_Application_Security_Testing/06-Session_Management_Testing/03-Testing_for_Session_Fixation/">OWASP WSTG: Testing for Session Fixation</a>.</li>
<li><strong>Also check:</strong> that the session ID never appears in a URL. OWASP notes that URLs leak through logs, history, bookmarks and the <code>Referer</code> header.</li>
</ul>
<h2>7. Logout and timeouts end the session (your own sites only)</h2>
<pre><code class="language-bash"># Replace with the cookie value you copied before logging out
curl -s -o /dev/null -w "%{http_code}\n" \
  -H "Cookie: session=OLD_VALUE" https://example.com/account
</code></pre>
<ul>
<li><strong>Expected:</strong> after logout, the old value no longer gives access (typically a redirect to login or a <code>401</code>/<code>403</code>). The same should happen after the idle and absolute timeouts you have set.</li>
<li><strong>Finding:</strong> the old value still returns account content. Clearing the cookie in the browser is not enough if the server still accepts it (<a href="https://wstg.owasp.org/v4.2/4-Web_Application_Security_Testing/06-Session_Management_Testing/06-Testing_for_Logout_Functionality/">OWASP WSTG: Testing for Logout Functionality</a>).</li>
<li><strong>Note:</strong> OWASP says timeout values depend on how critical the application and its data are, so they are a maintainer decision, not a fixed number.</li>
</ul>
<h2>Example: reading one result</h2>
<pre><code class="language-text">$ curl -sI https://example.com/login | grep -i '^set-cookie'
set-cookie: session=7f3c...; Path=/; Domain=example.com; HttpOnly
</code></pre>
<p><code>HttpOnly</code> is present. <code>Secure</code> and <code>SameSite</code> are missing, and <code>Domain</code> shares the cookie with every subdomain. Those are findings to fix, for example by renaming the cookie to <code>__Host-session</code> with <code>Secure; HttpOnly; SameSite=Lax; Path=/</code>. Even a perfect header line says nothing about renewal at login or invalidation at logout. Checks 6 and 7 cover that.</p>
<h2>Make it routine</h2>
<p>Re-run these checks whenever you change the login flow, add a subdomain or update session settings, and record the expected attributes for each cookie. A passing result is a starting point, not a guarantee.</p>
<p>Disclosure: we are the team behind HackerMe, a free automated website security scanner: <a href="https://hackerme.ai">https://hackerme.ai</a>. The checks above work without it.</p>
<p>What would you add to this list? We would like to hear it in the comments.</p>
]]></content:encoded></item><item><title><![CDATA[CORS Misconfigurations: 6 Checks, and What a Pass Doesn't Prove]]></title><description><![CDATA[Published by the HackerMe team (Agura), the makers of HackerMe, a free automated website security scanner. The checks below are vendor-neutral. Each is…]]></description><link>https://hackerme.hashnode.dev/cors-misconfigurations-6-checks-and-what-a-pass-doesn-t-prove</link><guid isPermaLink="true">https://hackerme.hashnode.dev/cors-misconfigurations-6-checks-and-what-a-pass-doesn-t-prove</guid><category><![CDATA[Security]]></category><category><![CDATA[webdev]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[api]]></category><dc:creator><![CDATA[HackerMe]]></dc:creator><pubDate>Mon, 02 Nov 2026 08:47:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ae84a848ebd91f5371a7b84/365e8d03-70b7-413a-9311-6053600f09a8.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Published by the HackerMe team (Agura), the makers of HackerMe, a free automated website security scanner. The checks below are vendor-neutral. Each is marked as a public check (you can run it from outside against your own site) or a maintainer check (it needs access to server configuration or code). Only test systems you own or have written permission to test.</em></p>
<p>Cross-Origin Resource Sharing (CORS) is a browser mechanism that lets a server say which other origins may read its responses. It is not an access control system for the server itself: it only controls what browsers let scripts from other origins read. Misconfigured CORS can let a malicious page read data that should only be visible to a logged-in user. This post covers six checks, with what you should see, what counts as a finding and what the check does not prove. Background: <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">MDN: CORS</a> and the <a href="https://portswigger.net/web-security/cors">PortSwigger CORS guide</a>.</p>
<h2>1. Is an arbitrary origin reflected? (public check)</h2>
<p>Send a request with an origin you control that is not on your allow list and read the response headers.</p>
<pre><code class="language-bash">curl -sI https://api.example.com/account \
  -H "Origin: https://untrusted.example" | grep -i access-control
</code></pre>
<ul>
<li><strong>Expected:</strong> no <code>Access-Control-Allow-Origin</code> header, or one that names only an origin you intended.</li>
<li><strong>Finding:</strong> the response echoes <code>https://untrusted.example</code> back.</li>
<li><strong>Doesn't prove:</strong> that the endpoint returns sensitive data. Reflection matters most on endpoints that return user-specific data.</li>
</ul>
<h2>2. Reflection combined with credentials (public check)</h2>
<pre><code class="language-bash">curl -sI https://api.example.com/account \
  -H "Origin: https://untrusted.example" | grep -i -E "access-control-allow-(origin|credentials)"
</code></pre>
<ul>
<li><strong>Expected:</strong> <code>Access-Control-Allow-Credentials: true</code> only together with a specific origin from your allow list.</li>
<li><strong>Finding:</strong> an untrusted origin reflected together with <code>Access-Control-Allow-Credentials: true</code>. That combination lets a page on that origin make requests with the visitor's cookies and read the responses.</li>
<li><strong>Note:</strong> browsers refuse to use credentials with <code>Access-Control-Allow-Origin: *</code>, so the wildcard alone does not carry that risk for credentialed requests. See <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Access-Control-Allow-Credentials">MDN: Access-Control-Allow-Credentials</a>.</li>
</ul>
<h2>3. The null origin (public check)</h2>
<pre><code class="language-bash">curl -sI https://api.example.com/account -H "Origin: null" | grep -i access-control
</code></pre>
<ul>
<li><strong>Expected:</strong> <code>null</code> is not allowed.</li>
<li><strong>Finding:</strong> <code>Access-Control-Allow-Origin: null</code>. Some sandboxed or local contexts send <code>null</code> as their origin, so allowing it widens access beyond what most teams intend.</li>
</ul>
<h2>4. Allow-list matching logic (public check plus maintainer check)</h2>
<p>Try origins that look similar to yours, such as <code>https://example.com.untrusted.example</code> and <code>https://untrustedexample.com</code>, and check whether either is accepted.</p>
<ul>
<li><strong>Expected:</strong> both are rejected, and only exact matches or a deliberate set of subdomains pass.</li>
<li><strong>Finding:</strong> an origin is accepted because it merely starts or ends with your domain. This usually points to a loose prefix, suffix or regular-expression match in the code, which only a maintainer can fix.</li>
<li><strong>Doesn't prove:</strong> that every endpoint uses the same logic. Check the API paths separately from the main site.</li>
</ul>
<h2>5. Caching and <code>Vary: Origin</code> (public check plus maintainer check)</h2>
<p>If the server returns different CORS headers depending on the request origin, shared caches need to know that.</p>
<pre><code class="language-bash">curl -sI https://api.example.com/public-data -H "Origin: https://allowed.example" | grep -i -E "vary|access-control"
</code></pre>
<ul>
<li><strong>Expected:</strong> <code>Vary: Origin</code> whenever <code>Access-Control-Allow-Origin</code> changes by origin.</li>
<li><strong>Finding:</strong> a dynamic allow-origin value with no <code>Vary: Origin</code>. A cache could serve a response meant for one origin to another.</li>
</ul>
<h2>6. Preflight handling (public check)</h2>
<p>Browsers send an <code>OPTIONS</code> preflight before some cross-origin requests. Check what it allows.</p>
<pre><code class="language-bash">curl -si -X OPTIONS https://api.example.com/account \
  -H "Origin: https://allowed.example" \
  -H "Access-Control-Request-Method: DELETE" \
  -H "Access-Control-Request-Headers: authorization"
</code></pre>
<ul>
<li><strong>Expected:</strong> <code>Access-Control-Allow-Methods</code> and <code>Access-Control-Allow-Headers</code> list only what the API needs.</li>
<li><strong>Finding:</strong> methods or headers that the API does not use, or a very long <code>Access-Control-Max-Age</code> that makes later policy changes slow to apply.</li>
<li><strong>Doesn't prove:</strong> that the API enforces authentication and authorization. CORS decides what browsers expose to other origins, and the server must still check every request.</li>
</ul>
<h2>Example: reading one result</h2>
<pre><code class="language-text">$ curl -sI https://api.example.com/account -H "Origin: https://untrusted.example" | grep -i -E "access-control"
access-control-allow-origin: https://untrusted.example
access-control-allow-credentials: true
</code></pre>
<p>The server reflects an untrusted origin and allows credentials. That is a finding to fix, typically by comparing the origin against a fixed allow list instead of echoing it. A clean output would show neither header for this origin. It would still say nothing about authentication, input validation or other classes of bugs.</p>
<h2>Make it routine</h2>
<p>Re-run these checks whenever you add an API, change a domain or touch CORS configuration, and keep a note of which origins are intentionally allowed. A passing result is a starting point, not a guarantee.</p>
<p>Disclosure: we are the team behind HackerMe, which runs automated checks against a public website for free: <a href="https://hackerme.ai">https://hackerme.ai</a>. The checklist above works without it.</p>
<p>What would you add to this list? We would like to hear it in the comments.</p>
]]></content:encoded></item></channel></rss>