Remediation Guides

Cookie Without HttpOnly Flag: How to Fix “Web Application Cookies Not Marked HttpOnly”

26 September 2026 8 min read

A cookie without HttpOnly flag finding means the server sends a Set-Cookie header lacking the HttpOnly attribute, so any JavaScript on the page, including script injected through cross-site scripting (XSS), can read that cookie via document.cookie. Fix it by adding HttpOnly to session and other sensitive cookies that client-side code never needs to read.

What the scanner is actually detecting

All of these checks are passive. The scanner reads the Set-Cookie response headers it collects while crawling and reports every cookie whose attribute list does not contain HttpOnly. Nothing is exploited, and no XSS has been found by this check.

Scanner Finding title ID Severity
Nessus Web Application Cookies Not Marked HttpOnly Plugin 85601 Info, family Web Servers
Tenable Web App Scanning Cookie Without HttpOnly Flag Detected Plugin 98063 Low (CVSS v3 3.1)
OWASP ZAP Cookie No HttpOnly Flag Alert 10010 (passive) Low
Burp Suite Cookie without HttpOnly flag set n/a Low (typical)

Tenable describes plugin 85601 as covering general cookies seen in both authenticated and unauthenticated sessions, and its solution text asks you to evaluate each cookie rather than flag them all blindly. Nessus also ships a narrower check, “Web Application Session Cookies Not Marked HttpOnly”, which looks only at session cookies and is rated Medium, so you may see both titles for the same application. ZAP’s rule is open source: it inspects both Set-Cookie and Set-Cookie2, skips cookies that are already expired, and ignores names on its cookie ignore list. Tenable WAS and ZAP map the issue to CWE-1004 (Sensitive Cookie Without ‘HttpOnly’ Flag).

Real-world risk

HttpOnly matters only when hostile script is already running in your origin, usually through an XSS flaw or a compromised third-party script. In that situation, a session cookie readable by JavaScript can be copied to an attacker’s server in one line of code and replayed from any machine until the session expires. With HttpOnly set, that theft path is closed.

Be precise about what the flag does not do. MDN notes that an HttpOnly cookie is still sent with JavaScript-initiated requests such as fetch() and XMLHttpRequest, so injected script can still act as the user inside the open page. HttpOnly limits the damage of XSS; it does not prevent or fix XSS. PortSwigger also points out that the issue is less significant when the cookie holds no sensitive data or other XSS defenses are in place. A missing flag on a session or “remember me” cookie deserves a fix; a missing flag on a theme preference cookie is close to irrelevant.

How to confirm it on the host

Request a page that sets cookies (use GET, and include the login page) and list the cookies that lack the attribute:

curl -sk -D - -o /dev/null https://www.example.com/login 
  | grep -i '^set-cookie' | grep -vi 'httponly'

Any line printed is a cookie without HttpOnly. On Windows:

$r = Invoke-WebRequest -Uri https://www.example.com/login -UseBasicParsing
$r.Headers['Set-Cookie']

The quickest test after login is in the browser. Sign in, open the developer tools console on the application and run document.cookie. Only script-readable cookies appear in that string, so if your session cookie name shows up, it is missing HttpOnly. The Application panel (Chrome, Edge) or Storage panel (Firefox) also has an HttpOnly column for every cookie.

Then find where the cookie is created:

php -i | grep session.cookie_httponly
sudo nginx -T | grep -i proxy_cookie_flags
sudo grep -Ri 'edit Set-Cookie' /etc/apache2/ /etc/httpd/ 2>/dev/null
findstr /s /i "httpOnlyCookies" C:inetpubwwwrootweb.config

How to fix it

Start with an inventory. List every cookie the scanner reported and ask one question per cookie: does legitimate client-side code read it? Session identifiers, authentication tokens and “remember me” cookies almost never need to be readable. CSRF tokens used by single-page frameworks often do. Fix in the application when you can, because that lets you choose per cookie; use the proxy as a fallback.

PHP

PHP’s own documentation lists session.cookie_httponly with a default of “0”, so the native session cookie is not HttpOnly unless you enable it. In the php.ini (or FPM pool file) that your web SAPI loads:

session.cookie_httponly = 1

Or in code, before session_start():

session_set_cookie_params(['httponly' => true, 'secure' => true, 'samesite' => 'Lax']);
session_start();

Cookies the application creates itself need the option too (PHP 7.3 and later accept an options array):

setcookie('remember', $token, ['httponly' => true, 'secure' => true, 'path' => '/']);

Restart PHP-FPM or the web server afterward. The CLI may read a different php.ini than FPM, so confirm with the live response headers, not only php -i.

ASP.NET on IIS (.NET Framework)

The httpOnlyCookies attribute defaults to false. Set it in web.config:

<system.web>
  <httpCookies httpOnlyCookies="true" requireSSL="true" />
</system.web>

ASP.NET Core

Apply a global policy with the cookie policy middleware, registered before any middleware that writes cookies:

builder.Services.Configure<CookiePolicyOptions>(options =>
{
    options.HttpOnly = Microsoft.AspNetCore.CookiePolicy.HttpOnlyPolicy.Always;
});
// ...
app.UseCookiePolicy();

For a single cookie, pass new CookieOptions { HttpOnly = true } to Response.Cookies.Append.

Java (Tomcat and other servlet containers)

Tomcat’s useHttpOnly Context attribute defaults to true, so JSESSIONID is normally already HttpOnly. If the finding names it, check whether someone set useHttpOnly=”false” in context.xml. You can also pin it in the application’s web.xml (Servlet 3.0 and later):

<session-config>
  <cookie-config>
    <http-only>true</http-only>
  </cookie-config>
</session-config>

Cookies created in code with new Cookie(…) are not covered by that setting; call cookie.setHttpOnly(true) before response.addCookie(cookie).

Node.js with Express

In express-session, cookie.httpOnly defaults to true, so look for code that sets it to false. For cookies set with res.cookie(), add the option explicitly:

res.cookie('remember_me', token, { httpOnly: true, secure: true, sameSite: 'lax' });

Apache HTTP Server as reverse proxy

With mod_headers enabled (sudo a2enmod headers on Debian and Ubuntu), rewrite the named cookie in the virtual host. Targeting a name avoids breaking cookies that scripts must read:

Header edit Set-Cookie "^(SESSIONID=.*)$" "$1; HttpOnly"
Header always edit Set-Cookie "^(SESSIONID=.*)$" "$1; HttpOnly"

Replace SESSIONID with the real cookie name, or use “^(.*)$” to cover every cookie. Both lines are intentional: the Apache documentation explains that always is not a superset of the default table, and headers from CGI scripts or mod_proxy_fcgi live only in the always table. Check with sudo apachectl configtest, then reload.

nginx 1.19.3 and later as reverse proxy

location / {
    proxy_pass http://127.0.0.1:8080;
    proxy_cookie_flags XSRF-TOKEN nohttponly;
    proxy_cookie_flags ~ httponly;
}

The ~ form is a regular expression matching every cookie name. nginx applies only the first directive that matches a cookie, so the XSRF-TOKEN line above exempts that token from the catch-all. The directive rewrites only responses from proxy_pass; for PHP-FPM behind fastcgi_pass, fix it in php.ini. Apply with sudo nginx -t && sudo systemctl reload nginx.

How to verify the fix and rescan

Repeat the curl check: the grep -vi ‘httponly’ pipeline should print nothing for the cookies you changed. Log in again in a fresh browser profile and confirm that document.cookie no longer shows the session cookie. Walk through login, logout, password reset and any “remember me” option, since some cookies are issued only there.

Then rescan with the same policy, the same credentials and the same target hostname. Plugin 85601 evaluates authenticated pages too, so an unauthenticated rescan can look clean while post-login cookies are still wrong, and a scan of the backend IP can differ from a scan through the load balancer.

What can break and how to roll back

  • CSRF tokens read by the frontend. Angular’s HttpClient reads the XSRF-TOKEN cookie and copies it into a header. Make that cookie HttpOnly and every state-changing request fails CSRF validation.
  • Single-page apps that read a token from a cookie. If the frontend pulls a JWT or user ID out of document.cookie, it will see nothing and treat the user as logged out.
  • Client-side logout or cookie cleanup. Script can no longer overwrite or delete an HttpOnly cookie, so logout must expire the cookie from the server.
  • Consent banners and analytics that check their own cookie in JavaScript, if a proxy-wide rule caught them.

Rollback is one configuration change: remove the directive or set the value back, then reload or restart the service. Cookies the browser already stored keep HttpOnly until they are reissued or expire, so affected users may need to sign out and back in.

Common false positive reasons

  • Cookies designed to be read by script. Django defaults CSRF_COOKIE_HTTPONLY to False, and its documentation says making the CSRF cookie HttpOnly offers no practical protection. Document these as accepted exceptions rather than breaking the application.
  • Non-sensitive cookies. Language, theme or layout cookies are technically correct findings with negligible risk.
  • Deletion cookies. A logout response that expires a cookie can be flagged by tools that do not skip expired cookies the way ZAP does.
  • Infrastructure cookies. Load balancer persistence or WAF cookies come from the device, not the application, and must be fixed in the device settings.
  • Wrong layer scanned. The proxy adds HttpOnly, but the scanner reached the backend directly.

Because this is runtime header behavior, dynamic scanners report it while code review often misses it; the differences between SAST, DAST and SCA explain why the tools disagree.

FAQ

Does setting HttpOnly fix XSS?

No. It stops script from reading the cookie, but injected script can still send requests that carry it. Fix the XSS itself as well.

Should every cookie be HttpOnly?

Every cookie that client-side code does not need. Session and authentication cookies always qualify; JavaScript-read CSRF tokens usually do not.

Is this the same as the “cookies not marked Secure” finding?

No. Secure controls whether the cookie travels over plain HTTP; HttpOnly controls JavaScript access. Most session cookies need both, and scanners report them separately.

Why is 85601 Info while another Nessus check is Medium?

Plugin 85601 lists every cookie without judging sensitivity. The session-cookie check reports only session cookies, where the missing flag has real impact.

Tracking this finding across many hosts

If you run several web scanners, SITEY, a self-hosted vulnerability management platform, can import findings from 16 of them into one place. Duplicates are merged per scanner, not across scanners, so the same cookie reported by Nessus 85601 and ZAP 10010 stays as two records. Per-finding retest works for Nessus, Acunetix and Burp findings; for ZAP or Tenable WAS, rerun the scan to confirm closure.

Sources

SITEY closes the loop, not just the report.Discover, validate, fix and verify in your own infrastructure.

See pricing